Merge "Remove StaticShadowHelper API abstractions." into pi-preview1-androidx-dev
diff --git a/app-toolkit/build.gradle b/app-toolkit/build.gradle
index 9bcb827..c771ed8 100644
--- a/app-toolkit/build.gradle
+++ b/app-toolkit/build.gradle
@@ -26,3 +26,5 @@
         classpath build_libs.kotlin.gradle_plugin
     }
 }
+
+apply from: 'buildSrc/jetify.gradle'
diff --git a/app-toolkit/common/api/0.0.0.txt b/app-toolkit/common/api_legacy/0.0.0.txt
similarity index 100%
rename from app-toolkit/common/api/0.0.0.txt
rename to app-toolkit/common/api_legacy/0.0.0.txt
diff --git a/app-toolkit/common/api/1.0.0.txt b/app-toolkit/common/api_legacy/1.0.0.txt
similarity index 100%
rename from app-toolkit/common/api/1.0.0.txt
rename to app-toolkit/common/api_legacy/1.0.0.txt
diff --git a/app-toolkit/common/api/1.1.0.txt b/app-toolkit/common/api_legacy/1.1.0.txt
similarity index 100%
rename from app-toolkit/common/api/1.1.0.txt
rename to app-toolkit/common/api_legacy/1.1.0.txt
diff --git a/app-toolkit/common/src/main/java/android/arch/core/internal/FastSafeIterableMap.java b/app-toolkit/common/src/main/java/android/arch/core/internal/FastSafeIterableMap.java
deleted file mode 100644
index dbd4d5f..0000000
--- a/app-toolkit/common/src/main/java/android/arch/core/internal/FastSafeIterableMap.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.internal;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Poor's man LinkedHashMap, which supports modifications during iterations.
- * Takes more memory that {@link SafeIterableMap}
- * It is NOT thread safe.
- *
- * @param <K> Key type
- * @param <V> Value type
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class FastSafeIterableMap<K, V> extends SafeIterableMap<K, V> {
-
-    private HashMap<K, Entry<K, V>> mHashMap = new HashMap<>();
-
-    @Override
-    protected Entry<K, V> get(K k) {
-        return mHashMap.get(k);
-    }
-
-    @Override
-    public V putIfAbsent(@NonNull K key, @NonNull V v) {
-        Entry<K, V> current = get(key);
-        if (current != null) {
-            return current.mValue;
-        }
-        mHashMap.put(key, put(key, v));
-        return null;
-    }
-
-    @Override
-    public V remove(@NonNull K key) {
-        V removed = super.remove(key);
-        mHashMap.remove(key);
-        return removed;
-    }
-
-    /**
-     * Returns {@code true} if this map contains a mapping for the specified
-     * key.
-     */
-    public boolean contains(K key) {
-        return mHashMap.containsKey(key);
-    }
-
-    /**
-     * Return an entry added to prior to an entry associated with the given key.
-     *
-     * @param k the key
-     */
-    public Map.Entry<K, V> ceil(K k) {
-        if (contains(k)) {
-            return mHashMap.get(k).mPrevious;
-        }
-        return null;
-    }
-}
diff --git a/app-toolkit/common/src/main/java/android/arch/core/internal/SafeIterableMap.java b/app-toolkit/common/src/main/java/android/arch/core/internal/SafeIterableMap.java
deleted file mode 100644
index 00e102f..0000000
--- a/app-toolkit/common/src/main/java/android/arch/core/internal/SafeIterableMap.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.internal;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
-import java.util.Iterator;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * LinkedList, which pretends to be a map and supports modifications during iterations.
- * It is NOT thread safe.
- *
- * @param <K> Key type
- * @param <V> Value type
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class SafeIterableMap<K, V> implements Iterable<Map.Entry<K, V>> {
-
-    private Entry<K, V> mStart;
-    private Entry<K, V> mEnd;
-    // using WeakHashMap over List<WeakReference>, so we don't have to manually remove
-    // WeakReferences that have null in them.
-    private WeakHashMap<SupportRemove<K, V>, Boolean> mIterators = new WeakHashMap<>();
-    private int mSize = 0;
-
-    protected Entry<K, V> get(K k) {
-        Entry<K, V> currentNode = mStart;
-        while (currentNode != null) {
-            if (currentNode.mKey.equals(k)) {
-                break;
-            }
-            currentNode = currentNode.mNext;
-        }
-        return currentNode;
-    }
-
-    /**
-     * If the specified key is not already associated
-     * with a value, associates it with the given value.
-     *
-     * @param key key with which the specified value is to be associated
-     * @param v   value to be associated with the specified key
-     * @return the previous value associated with the specified key,
-     * or {@code null} if there was no mapping for the key
-     */
-    public V putIfAbsent(@NonNull K key, @NonNull V v) {
-        Entry<K, V> entry = get(key);
-        if (entry != null) {
-            return entry.mValue;
-        }
-        put(key, v);
-        return null;
-    }
-
-    protected Entry<K, V> put(@NonNull K key, @NonNull V v) {
-        Entry<K, V> newEntry = new Entry<>(key, v);
-        mSize++;
-        if (mEnd == null) {
-            mStart = newEntry;
-            mEnd = mStart;
-            return newEntry;
-        }
-
-        mEnd.mNext = newEntry;
-        newEntry.mPrevious = mEnd;
-        mEnd = newEntry;
-        return newEntry;
-
-    }
-
-    /**
-     * Removes the mapping for a key from this map if it is present.
-     *
-     * @param key key whose mapping is to be removed from the map
-     * @return the previous value associated with the specified key,
-     * or {@code null} if there was no mapping for the key
-     */
-    public V remove(@NonNull K key) {
-        Entry<K, V> toRemove = get(key);
-        if (toRemove == null) {
-            return null;
-        }
-        mSize--;
-        if (!mIterators.isEmpty()) {
-            for (SupportRemove<K, V> iter : mIterators.keySet()) {
-                iter.supportRemove(toRemove);
-            }
-        }
-
-        if (toRemove.mPrevious != null) {
-            toRemove.mPrevious.mNext = toRemove.mNext;
-        } else {
-            mStart = toRemove.mNext;
-        }
-
-        if (toRemove.mNext != null) {
-            toRemove.mNext.mPrevious = toRemove.mPrevious;
-        } else {
-            mEnd = toRemove.mPrevious;
-        }
-
-        toRemove.mNext = null;
-        toRemove.mPrevious = null;
-        return toRemove.mValue;
-    }
-
-    /**
-     * @return the number of elements in this map
-     */
-    public int size() {
-        return mSize;
-    }
-
-    /**
-     * @return an ascending iterator, which doesn't include new elements added during an
-     * iteration.
-     */
-    @NonNull
-    @Override
-    public Iterator<Map.Entry<K, V>> iterator() {
-        ListIterator<K, V> iterator = new AscendingIterator<>(mStart, mEnd);
-        mIterators.put(iterator, false);
-        return iterator;
-    }
-
-    /**
-     * @return an descending iterator, which doesn't include new elements added during an
-     * iteration.
-     */
-    public Iterator<Map.Entry<K, V>> descendingIterator() {
-        DescendingIterator<K, V> iterator = new DescendingIterator<>(mEnd, mStart);
-        mIterators.put(iterator, false);
-        return iterator;
-    }
-
-    /**
-     * return an iterator with additions.
-     */
-    public IteratorWithAdditions iteratorWithAdditions() {
-        @SuppressWarnings("unchecked")
-        IteratorWithAdditions iterator = new IteratorWithAdditions();
-        mIterators.put(iterator, false);
-        return iterator;
-    }
-
-    /**
-     * @return eldest added entry or null
-     */
-    public Map.Entry<K, V> eldest() {
-        return mStart;
-    }
-
-    /**
-     * @return newest added entry or null
-     */
-    public Map.Entry<K, V> newest() {
-        return mEnd;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof SafeIterableMap)) {
-            return false;
-        }
-        SafeIterableMap map = (SafeIterableMap) obj;
-        if (this.size() != map.size()) {
-            return false;
-        }
-        Iterator<Map.Entry<K, V>> iterator1 = iterator();
-        Iterator iterator2 = map.iterator();
-        while (iterator1.hasNext() && iterator2.hasNext()) {
-            Map.Entry<K, V> next1 = iterator1.next();
-            Object next2 = iterator2.next();
-            if ((next1 == null && next2 != null)
-                    || (next1 != null && !next1.equals(next2))) {
-                return false;
-            }
-        }
-        return !iterator1.hasNext() && !iterator2.hasNext();
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("[");
-        Iterator<Map.Entry<K, V>> iterator = iterator();
-        while (iterator.hasNext()) {
-            builder.append(iterator.next().toString());
-            if (iterator.hasNext()) {
-                builder.append(", ");
-            }
-        }
-        builder.append("]");
-        return builder.toString();
-    }
-
-    private abstract static class ListIterator<K, V> implements Iterator<Map.Entry<K, V>>,
-            SupportRemove<K, V> {
-        Entry<K, V> mExpectedEnd;
-        Entry<K, V> mNext;
-
-        ListIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
-            this.mExpectedEnd = expectedEnd;
-            this.mNext = start;
-        }
-
-        @Override
-        public boolean hasNext() {
-            return mNext != null;
-        }
-
-        @Override
-        public void supportRemove(@NonNull Entry<K, V> entry) {
-            if (mExpectedEnd == entry && entry == mNext) {
-                mNext = null;
-                mExpectedEnd = null;
-            }
-
-            if (mExpectedEnd == entry) {
-                mExpectedEnd = backward(mExpectedEnd);
-            }
-
-            if (mNext == entry) {
-                mNext = nextNode();
-            }
-        }
-
-        private Entry<K, V> nextNode() {
-            if (mNext == mExpectedEnd || mExpectedEnd == null) {
-                return null;
-            }
-            return forward(mNext);
-        }
-
-        @Override
-        public Map.Entry<K, V> next() {
-            Map.Entry<K, V> result = mNext;
-            mNext = nextNode();
-            return result;
-        }
-
-        abstract Entry<K, V> forward(Entry<K, V> entry);
-
-        abstract Entry<K, V> backward(Entry<K, V> entry);
-    }
-
-    static class AscendingIterator<K, V> extends ListIterator<K, V> {
-        AscendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
-            super(start, expectedEnd);
-        }
-
-        @Override
-        Entry<K, V> forward(Entry<K, V> entry) {
-            return entry.mNext;
-        }
-
-        @Override
-        Entry<K, V> backward(Entry<K, V> entry) {
-            return entry.mPrevious;
-        }
-    }
-
-    private static class DescendingIterator<K, V> extends ListIterator<K, V> {
-
-        DescendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
-            super(start, expectedEnd);
-        }
-
-        @Override
-        Entry<K, V> forward(Entry<K, V> entry) {
-            return entry.mPrevious;
-        }
-
-        @Override
-        Entry<K, V> backward(Entry<K, V> entry) {
-            return entry.mNext;
-        }
-    }
-
-    private class IteratorWithAdditions implements Iterator<Map.Entry<K, V>>, SupportRemove<K, V> {
-        private Entry<K, V> mCurrent;
-        private boolean mBeforeStart = true;
-
-        @Override
-        public void supportRemove(@NonNull Entry<K, V> entry) {
-            if (entry == mCurrent) {
-                mCurrent = mCurrent.mPrevious;
-                mBeforeStart = mCurrent == null;
-            }
-        }
-
-        @Override
-        public boolean hasNext() {
-            if (mBeforeStart) {
-                return mStart != null;
-            }
-            return mCurrent != null && mCurrent.mNext != null;
-        }
-
-        @Override
-        public Map.Entry<K, V> next() {
-            if (mBeforeStart) {
-                mBeforeStart = false;
-                mCurrent = mStart;
-            } else {
-                mCurrent = mCurrent != null ? mCurrent.mNext : null;
-            }
-            return mCurrent;
-        }
-    }
-
-    interface SupportRemove<K, V> {
-        void supportRemove(@NonNull Entry<K, V> entry);
-    }
-
-    static class Entry<K, V> implements Map.Entry<K, V> {
-        @NonNull
-        final K mKey;
-        @NonNull
-        final V mValue;
-        Entry<K, V> mNext;
-        Entry<K, V> mPrevious;
-
-        Entry(@NonNull K key, @NonNull V value) {
-            mKey = key;
-            this.mValue = value;
-        }
-
-        @NonNull
-        @Override
-        public K getKey() {
-            return mKey;
-        }
-
-        @NonNull
-        @Override
-        public V getValue() {
-            return mValue;
-        }
-
-        @Override
-        public V setValue(V value) {
-            throw new UnsupportedOperationException("An entry modification is not supported");
-        }
-
-        @Override
-        public String toString() {
-            return mKey + "=" + mValue;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == this) {
-                return true;
-            }
-            if (!(obj instanceof Entry)) {
-                return false;
-            }
-            Entry entry = (Entry) obj;
-            return mKey.equals(entry.mKey) && mValue.equals(entry.mValue);
-        }
-    }
-}
diff --git a/app-toolkit/common/src/main/java/android/arch/core/util/Function.java b/app-toolkit/common/src/main/java/android/arch/core/util/Function.java
deleted file mode 100644
index 25e7a6b..0000000
--- a/app-toolkit/common/src/main/java/android/arch/core/util/Function.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.util;
-
-/**
- * Represents a function.
- *
- * @param <I> the type of the input to the function
- * @param <O> the type of the output of the function
- */
-public interface Function<I, O> {
-    /**
-     * Applies this function to the given input.
-     *
-     * @param input the input
-     * @return the function result.
-     */
-    O apply(I input);
-}
diff --git a/app-toolkit/common/src/main/java/androidx/arch/core/internal/FastSafeIterableMap.java b/app-toolkit/common/src/main/java/androidx/arch/core/internal/FastSafeIterableMap.java
new file mode 100644
index 0000000..c3a9086
--- /dev/null
+++ b/app-toolkit/common/src/main/java/androidx/arch/core/internal/FastSafeIterableMap.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018 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 androidx.arch.core.internal;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Poor's man LinkedHashMap, which supports modifications during iterations.
+ * Takes more memory that {@link SafeIterableMap}
+ * It is NOT thread safe.
+ *
+ * @param <K> Key type
+ * @param <V> Value type
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FastSafeIterableMap<K, V> extends SafeIterableMap<K, V> {
+
+    private HashMap<K, Entry<K, V>> mHashMap = new HashMap<>();
+
+    @Override
+    protected Entry<K, V> get(K k) {
+        return mHashMap.get(k);
+    }
+
+    @Override
+    public V putIfAbsent(@NonNull K key, @NonNull V v) {
+        Entry<K, V> current = get(key);
+        if (current != null) {
+            return current.mValue;
+        }
+        mHashMap.put(key, put(key, v));
+        return null;
+    }
+
+    @Override
+    public V remove(@NonNull K key) {
+        V removed = super.remove(key);
+        mHashMap.remove(key);
+        return removed;
+    }
+
+    /**
+     * Returns {@code true} if this map contains a mapping for the specified
+     * key.
+     */
+    public boolean contains(K key) {
+        return mHashMap.containsKey(key);
+    }
+
+    /**
+     * Return an entry added to prior to an entry associated with the given key.
+     *
+     * @param k the key
+     */
+    public Map.Entry<K, V> ceil(K k) {
+        if (contains(k)) {
+            return mHashMap.get(k).mPrevious;
+        }
+        return null;
+    }
+}
diff --git a/app-toolkit/common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java b/app-toolkit/common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
new file mode 100644
index 0000000..ef09098
--- /dev/null
+++ b/app-toolkit/common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2018 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 androidx.arch.core.internal;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * LinkedList, which pretends to be a map and supports modifications during iterations.
+ * It is NOT thread safe.
+ *
+ * @param <K> Key type
+ * @param <V> Value type
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SafeIterableMap<K, V> implements Iterable<Map.Entry<K, V>> {
+
+    private Entry<K, V> mStart;
+    private Entry<K, V> mEnd;
+    // using WeakHashMap over List<WeakReference>, so we don't have to manually remove
+    // WeakReferences that have null in them.
+    private WeakHashMap<SupportRemove<K, V>, Boolean> mIterators = new WeakHashMap<>();
+    private int mSize = 0;
+
+    protected Entry<K, V> get(K k) {
+        Entry<K, V> currentNode = mStart;
+        while (currentNode != null) {
+            if (currentNode.mKey.equals(k)) {
+                break;
+            }
+            currentNode = currentNode.mNext;
+        }
+        return currentNode;
+    }
+
+    /**
+     * If the specified key is not already associated
+     * with a value, associates it with the given value.
+     *
+     * @param key key with which the specified value is to be associated
+     * @param v   value to be associated with the specified key
+     * @return the previous value associated with the specified key,
+     * or {@code null} if there was no mapping for the key
+     */
+    public V putIfAbsent(@NonNull K key, @NonNull V v) {
+        Entry<K, V> entry = get(key);
+        if (entry != null) {
+            return entry.mValue;
+        }
+        put(key, v);
+        return null;
+    }
+
+    protected Entry<K, V> put(@NonNull K key, @NonNull V v) {
+        Entry<K, V> newEntry = new Entry<>(key, v);
+        mSize++;
+        if (mEnd == null) {
+            mStart = newEntry;
+            mEnd = mStart;
+            return newEntry;
+        }
+
+        mEnd.mNext = newEntry;
+        newEntry.mPrevious = mEnd;
+        mEnd = newEntry;
+        return newEntry;
+
+    }
+
+    /**
+     * Removes the mapping for a key from this map if it is present.
+     *
+     * @param key key whose mapping is to be removed from the map
+     * @return the previous value associated with the specified key,
+     * or {@code null} if there was no mapping for the key
+     */
+    public V remove(@NonNull K key) {
+        Entry<K, V> toRemove = get(key);
+        if (toRemove == null) {
+            return null;
+        }
+        mSize--;
+        if (!mIterators.isEmpty()) {
+            for (SupportRemove<K, V> iter : mIterators.keySet()) {
+                iter.supportRemove(toRemove);
+            }
+        }
+
+        if (toRemove.mPrevious != null) {
+            toRemove.mPrevious.mNext = toRemove.mNext;
+        } else {
+            mStart = toRemove.mNext;
+        }
+
+        if (toRemove.mNext != null) {
+            toRemove.mNext.mPrevious = toRemove.mPrevious;
+        } else {
+            mEnd = toRemove.mPrevious;
+        }
+
+        toRemove.mNext = null;
+        toRemove.mPrevious = null;
+        return toRemove.mValue;
+    }
+
+    /**
+     * @return the number of elements in this map
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * @return an ascending iterator, which doesn't include new elements added during an
+     * iteration.
+     */
+    @NonNull
+    @Override
+    public Iterator<Map.Entry<K, V>> iterator() {
+        ListIterator<K, V> iterator = new AscendingIterator<>(mStart, mEnd);
+        mIterators.put(iterator, false);
+        return iterator;
+    }
+
+    /**
+     * @return an descending iterator, which doesn't include new elements added during an
+     * iteration.
+     */
+    public Iterator<Map.Entry<K, V>> descendingIterator() {
+        DescendingIterator<K, V> iterator = new DescendingIterator<>(mEnd, mStart);
+        mIterators.put(iterator, false);
+        return iterator;
+    }
+
+    /**
+     * return an iterator with additions.
+     */
+    public IteratorWithAdditions iteratorWithAdditions() {
+        @SuppressWarnings("unchecked")
+        IteratorWithAdditions iterator = new IteratorWithAdditions();
+        mIterators.put(iterator, false);
+        return iterator;
+    }
+
+    /**
+     * @return eldest added entry or null
+     */
+    public Map.Entry<K, V> eldest() {
+        return mStart;
+    }
+
+    /**
+     * @return newest added entry or null
+     */
+    public Map.Entry<K, V> newest() {
+        return mEnd;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof SafeIterableMap)) {
+            return false;
+        }
+        SafeIterableMap map = (SafeIterableMap) obj;
+        if (this.size() != map.size()) {
+            return false;
+        }
+        Iterator<Map.Entry<K, V>> iterator1 = iterator();
+        Iterator iterator2 = map.iterator();
+        while (iterator1.hasNext() && iterator2.hasNext()) {
+            Map.Entry<K, V> next1 = iterator1.next();
+            Object next2 = iterator2.next();
+            if ((next1 == null && next2 != null)
+                    || (next1 != null && !next1.equals(next2))) {
+                return false;
+            }
+        }
+        return !iterator1.hasNext() && !iterator2.hasNext();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("[");
+        Iterator<Map.Entry<K, V>> iterator = iterator();
+        while (iterator.hasNext()) {
+            builder.append(iterator.next().toString());
+            if (iterator.hasNext()) {
+                builder.append(", ");
+            }
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+
+    private abstract static class ListIterator<K, V> implements Iterator<Map.Entry<K, V>>,
+            SupportRemove<K, V> {
+        Entry<K, V> mExpectedEnd;
+        Entry<K, V> mNext;
+
+        ListIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
+            this.mExpectedEnd = expectedEnd;
+            this.mNext = start;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mNext != null;
+        }
+
+        @Override
+        public void supportRemove(@NonNull Entry<K, V> entry) {
+            if (mExpectedEnd == entry && entry == mNext) {
+                mNext = null;
+                mExpectedEnd = null;
+            }
+
+            if (mExpectedEnd == entry) {
+                mExpectedEnd = backward(mExpectedEnd);
+            }
+
+            if (mNext == entry) {
+                mNext = nextNode();
+            }
+        }
+
+        private Entry<K, V> nextNode() {
+            if (mNext == mExpectedEnd || mExpectedEnd == null) {
+                return null;
+            }
+            return forward(mNext);
+        }
+
+        @Override
+        public Map.Entry<K, V> next() {
+            Map.Entry<K, V> result = mNext;
+            mNext = nextNode();
+            return result;
+        }
+
+        abstract Entry<K, V> forward(Entry<K, V> entry);
+
+        abstract Entry<K, V> backward(Entry<K, V> entry);
+    }
+
+    static class AscendingIterator<K, V> extends ListIterator<K, V> {
+        AscendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
+            super(start, expectedEnd);
+        }
+
+        @Override
+        Entry<K, V> forward(Entry<K, V> entry) {
+            return entry.mNext;
+        }
+
+        @Override
+        Entry<K, V> backward(Entry<K, V> entry) {
+            return entry.mPrevious;
+        }
+    }
+
+    private static class DescendingIterator<K, V> extends ListIterator<K, V> {
+
+        DescendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
+            super(start, expectedEnd);
+        }
+
+        @Override
+        Entry<K, V> forward(Entry<K, V> entry) {
+            return entry.mPrevious;
+        }
+
+        @Override
+        Entry<K, V> backward(Entry<K, V> entry) {
+            return entry.mNext;
+        }
+    }
+
+    private class IteratorWithAdditions implements Iterator<Map.Entry<K, V>>, SupportRemove<K, V> {
+        private Entry<K, V> mCurrent;
+        private boolean mBeforeStart = true;
+
+        @Override
+        public void supportRemove(@NonNull Entry<K, V> entry) {
+            if (entry == mCurrent) {
+                mCurrent = mCurrent.mPrevious;
+                mBeforeStart = mCurrent == null;
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (mBeforeStart) {
+                return mStart != null;
+            }
+            return mCurrent != null && mCurrent.mNext != null;
+        }
+
+        @Override
+        public Map.Entry<K, V> next() {
+            if (mBeforeStart) {
+                mBeforeStart = false;
+                mCurrent = mStart;
+            } else {
+                mCurrent = mCurrent != null ? mCurrent.mNext : null;
+            }
+            return mCurrent;
+        }
+    }
+
+    interface SupportRemove<K, V> {
+        void supportRemove(@NonNull Entry<K, V> entry);
+    }
+
+    static class Entry<K, V> implements Map.Entry<K, V> {
+        @NonNull
+        final K mKey;
+        @NonNull
+        final V mValue;
+        Entry<K, V> mNext;
+        Entry<K, V> mPrevious;
+
+        Entry(@NonNull K key, @NonNull V value) {
+            mKey = key;
+            this.mValue = value;
+        }
+
+        @NonNull
+        @Override
+        public K getKey() {
+            return mKey;
+        }
+
+        @NonNull
+        @Override
+        public V getValue() {
+            return mValue;
+        }
+
+        @Override
+        public V setValue(V value) {
+            throw new UnsupportedOperationException("An entry modification is not supported");
+        }
+
+        @Override
+        public String toString() {
+            return mKey + "=" + mValue;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof Entry)) {
+                return false;
+            }
+            Entry entry = (Entry) obj;
+            return mKey.equals(entry.mKey) && mValue.equals(entry.mValue);
+        }
+    }
+}
diff --git a/app-toolkit/common/src/main/java/androidx/arch/core/util/Function.java b/app-toolkit/common/src/main/java/androidx/arch/core/util/Function.java
new file mode 100644
index 0000000..72bde2f
--- /dev/null
+++ b/app-toolkit/common/src/main/java/androidx/arch/core/util/Function.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 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 androidx.arch.core.util;
+
+/**
+ * Represents a function.
+ *
+ * @param <I> the type of the input to the function
+ * @param <O> the type of the output of the function
+ */
+public interface Function<I, O> {
+    /**
+     * Applies this function to the given input.
+     *
+     * @param input the input
+     * @return the function result.
+     */
+    O apply(I input);
+}
diff --git a/app-toolkit/common/src/test/java/android/arch/core/internal/FastSafeIterableMapTest.java b/app-toolkit/common/src/test/java/android/arch/core/internal/FastSafeIterableMapTest.java
deleted file mode 100644
index 41b1497..0000000
--- a/app-toolkit/common/src/test/java/android/arch/core/internal/FastSafeIterableMapTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.internal;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class FastSafeIterableMapTest {
-    @Test
-    public void testCeil() {
-        FastSafeIterableMap<Integer, Boolean> map = new FastSafeIterableMap<>();
-        assertThat(map.ceil(1), nullValue());
-        map.putIfAbsent(1, false);
-        assertThat(map.ceil(1), nullValue());
-        map.putIfAbsent(2, false);
-        assertThat(map.ceil(2).getKey(), is(1));
-        map.remove(1);
-        assertThat(map.ceil(2), nullValue());
-    }
-
-    @Test
-    public void testPut() {
-        FastSafeIterableMap<Integer, Integer> map = new FastSafeIterableMap<>();
-        map.putIfAbsent(10, 20);
-        map.putIfAbsent(20, 40);
-        map.putIfAbsent(30, 60);
-        assertThat(map.putIfAbsent(5, 10), is((Integer) null));
-        assertThat(map.putIfAbsent(10, 30), is(20));
-    }
-
-    @Test
-    public void testContains() {
-        FastSafeIterableMap<Integer, Integer> map = new FastSafeIterableMap<>();
-        map.putIfAbsent(10, 20);
-        map.putIfAbsent(20, 40);
-        map.putIfAbsent(30, 60);
-        assertThat(map.contains(10), is(true));
-        assertThat(map.contains(11), is(false));
-        assertThat(new FastSafeIterableMap<Integer, Integer>().contains(0), is(false));
-    }
-
-
-    @Test
-    public void testRemove() {
-        FastSafeIterableMap<Integer, Integer> map = new FastSafeIterableMap<>();
-        map.putIfAbsent(10, 20);
-        map.putIfAbsent(20, 40);
-        assertThat(map.contains(10), is(true));
-        assertThat(map.contains(20), is(true));
-        assertThat(map.remove(10), is(20));
-        assertThat(map.contains(10), is(false));
-        assertThat(map.putIfAbsent(10, 30), nullValue());
-        assertThat(map.putIfAbsent(10, 40), is(30));
-    }
-}
diff --git a/app-toolkit/common/src/test/java/android/arch/core/internal/SafeIterableMapTest.java b/app-toolkit/common/src/test/java/android/arch/core/internal/SafeIterableMapTest.java
deleted file mode 100644
index d879543..0000000
--- a/app-toolkit/common/src/test/java/android/arch/core/internal/SafeIterableMapTest.java
+++ /dev/null
@@ -1,505 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.internal;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Map.Entry;
-
-@RunWith(JUnit4.class)
-public class SafeIterableMapTest {
-
-    @Test
-    public void testToString() {
-        SafeIterableMap<Integer, String> map = from(1, 2, 3, 4).to("a", "b", "c", "d");
-        assertThat(map.toString(), is("[1=a, 2=b, 3=c, 4=d]"));
-    }
-
-    @Test
-    public void testEmptyToString() {
-        SafeIterableMap<Integer, Boolean> map = mapOf();
-        assertThat(map.toString(), is("[]"));
-    }
-
-    @Test
-    public void testOneElementToString() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1);
-        assertThat(map.toString(), is("[1=true]"));
-    }
-
-
-    @Test
-    public void testEquality1() {
-        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(10, 20, 30, 40);
-        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(10, 20, 30, 40);
-        assertThat(map1.equals(map2), is(true));
-    }
-
-    @Test
-    public void testEquality2() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        //noinspection ObjectEqualsNull
-        assertThat(map.equals(null), is(false));
-    }
-
-    @Test
-    public void testEquality3() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        //noinspection EqualsBetweenInconvertibleTypes
-        assertThat(map.equals(new ArrayList<>()), is(false));
-    }
-
-    @Test
-    public void testEquality4() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        assertThat(map.equals(new SafeIterableMap<Integer, Boolean>()), is(false));
-    }
-
-    @Test
-    public void testEquality5() {
-        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
-        SafeIterableMap<Integer, Boolean> map2 = mapOf(1);
-        assertThat(map1.equals(map2), is(false));
-    }
-
-    @Test
-    public void testEquality6() {
-        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
-        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 2, 3, 5);
-        assertThat(map1.equals(map2), is(false));
-    }
-
-    @Test
-    public void testEquality7() {
-        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(1, 2, 3, 4);
-        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(1, 2, 3, 5);
-        assertThat(map1.equals(map2), is(false));
-    }
-
-
-    @Test
-    public void testEquality8() {
-        SafeIterableMap<Integer, Boolean> map1 = mapOf();
-        SafeIterableMap<Integer, Boolean> map2 = mapOf();
-        assertThat(map1.equals(map2), is(true));
-    }
-
-    @Test
-    public void testEqualityRespectsOrder() {
-        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
-        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 3, 2, 4);
-        assertThat(map1.equals(map2), is(false));
-    }
-
-    @Test
-    public void testPut() {
-        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
-        assertThat(map.putIfAbsent(5, 10), is((Integer) null));
-        assertThat(map, is(from(1, 2, 3, 4, 5).to(10, 20, 30, 40, 10)));
-    }
-
-    @Test
-    public void testAddExisted() {
-        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 261, 40);
-        assertThat(map.putIfAbsent(3, 239), is(261));
-        assertThat(map, is(from(1, 2, 3, 4).to(10, 20, 261, 40)));
-    }
-
-    @Test
-    public void testRemoveLast() {
-        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
-        assertThat(map.remove(4), is(40));
-        assertThat(map, is(from(1, 2, 3).to(10, 20, 30)));
-    }
-
-    @Test
-    public void testRemoveFirst() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        assertThat(map.remove(1), is(true));
-        assertThat(map, is(mapOf(2, 3, 4)));
-    }
-
-    @Test
-    public void testRemoveMiddle() {
-        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
-        assertThat(map.remove(2), is(20));
-        assertThat(map.remove(3), is(30));
-        assertThat(map, is(from(1, 4).to(10, 40)));
-    }
-
-    @Test
-    public void testRemoveNotExisted() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        assertThat(map.remove(5), is((Boolean) null));
-        assertThat(map, is(mapOf(1, 2, 3, 4)));
-    }
-
-    @Test
-    public void testRemoveSole() {
-        SafeIterableMap<Integer, Integer> map = from(1).to(261);
-        assertThat(map.remove(1), is(261));
-        assertThat(map, is(new SafeIterableMap<Integer, Integer>()));
-    }
-
-    @Test
-    public void testRemoveDuringIteration1() {
-        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
-        int index = 0;
-        int[] expected = new int[]{1, 4};
-        for (Entry<Integer, Integer> i : map) {
-            assertThat(i.getKey(), is(expected[index++]));
-            if (index == 1) {
-                assertThat(map.remove(2), is(20));
-                assertThat(map.remove(3), is(30));
-            }
-        }
-    }
-
-    @Test
-    public void testRemoveDuringIteration2() {
-        SafeIterableMap<Integer, Integer> map = from(1, 2).to(10, 20);
-        Iterator<Entry<Integer, Integer>> iter = map.iterator();
-        assertThat(map.remove(2), is(20));
-        assertThat(map.remove(1), is(10));
-        assertThat(iter.hasNext(), is(false));
-    }
-
-    @Test
-    public void testRemoveDuringIteration3() {
-        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
-        int index = 0;
-        Iterator<Entry<Integer, Integer>> iter = map.iterator();
-        assertThat(map.remove(1), is(10));
-        assertThat(map.remove(2), is(20));
-        int[] expected = new int[]{3, 4};
-        while (iter.hasNext()) {
-            assertThat(iter.next().getKey(), is(expected[index++]));
-        }
-    }
-
-    @Test
-    public void testRemoveDuringIteration4() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2);
-        int[] expected = new int[]{1, 2};
-        int index = 0;
-        for (Entry<Integer, Boolean> entry : map) {
-            assertThat(entry.getKey(), is(expected[index++]));
-            if (index == 1) {
-                map.remove(1);
-            }
-        }
-        assertThat(index, is(2));
-    }
-
-    @Test
-    public void testAdditionDuringIteration() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{1, 2, 3, 4};
-        int index = 0;
-        for (Entry<Integer, Boolean> entry : map) {
-            assertThat(entry.getKey(), is(expected[index++]));
-            if (index == 1) {
-                map.putIfAbsent(5, true);
-            }
-        }
-    }
-
-    @Test
-    public void testReAdditionDuringIteration() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{1, 2, 4};
-        int index = 0;
-        for (Entry<Integer, Boolean> entry : map) {
-            assertThat(entry.getKey(), is(expected[index++]));
-            if (index == 1) {
-                map.remove(3);
-                map.putIfAbsent(3, true);
-            }
-        }
-    }
-
-    @Test
-    public void testSize() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        assertThat(map.size(), is(4));
-        map.putIfAbsent(5, true);
-        map.putIfAbsent(6, true);
-        assertThat(map.size(), is(6));
-        map.remove(5);
-        map.remove(5);
-        assertThat(map.size(), is(5));
-        map.remove(1);
-        map.remove(2);
-        map.remove(4);
-        map.remove(3);
-        map.remove(6);
-        assertThat(map.size(), is(0));
-        map.putIfAbsent(4, true);
-        assertThat(map.size(), is(1));
-        assertThat(mapOf().size(), is(0));
-    }
-
-    @Test
-    public void testIteratorWithAdditions1() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{1, 2, 3, 5};
-        int index = 0;
-        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
-        while (iterator.hasNext()) {
-            Entry<Integer, Boolean> entry = iterator.next();
-            assertThat(entry.getKey(), is(expected[index++]));
-            if (index == 3) {
-                map.remove(4);
-                map.putIfAbsent(5, true);
-            }
-        }
-    }
-
-    @Test
-    public void testIteratorWithAdditions2() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1);
-        int[] expected = new int[]{1, 2, 3};
-        int index = 0;
-        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
-        while (iterator.hasNext()) {
-            Entry<Integer, Boolean> entry = iterator.next();
-            assertThat(entry.getKey(), is(expected[index++]));
-            if (index == 1) {
-                map.putIfAbsent(2, true);
-                map.putIfAbsent(3, true);
-            }
-        }
-        assertThat(index, is(3));
-    }
-
-
-    @Test
-    public void testIteratorWithAdditions3() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3);
-        int[] expected = new int[]{1};
-        int index = 0;
-        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
-        while (iterator.hasNext()) {
-            Entry<Integer, Boolean> entry = iterator.next();
-            assertThat(entry.getKey(), is(expected[index++]));
-            map.remove(2);
-            map.remove(3);
-        }
-        assertThat(index, is(1));
-    }
-
-    @Test
-    public void testIteratorWithAdditions4() {
-        SafeIterableMap<Integer, Boolean> map = mapOf();
-        int[] expected = new int[]{1, 2, 3};
-        int index = 0;
-        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
-        map.putIfAbsent(1, true);
-        while (iterator.hasNext()) {
-            Entry<Integer, Boolean> entry = iterator.next();
-            assertThat(entry.getKey(), is(expected[index++]));
-            if (index == 1) {
-                map.putIfAbsent(2, false);
-            }
-            if (index == 2) {
-                map.putIfAbsent(3, false);
-            }
-        }
-        assertThat(index, is(3));
-    }
-
-    @Test
-    public void testIteratorWithAddition5() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2);
-        int[] expected = new int[]{1, 2};
-        int index = 0;
-        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
-        while (iterator.hasNext()) {
-            Entry<Integer, Boolean> entry = iterator.next();
-            assertThat(entry.getKey(), is(expected[index++]));
-            if (index == 1) {
-                map.remove(1);
-            }
-        }
-        assertThat(index, is(2));
-    }
-
-    @Test
-    public void testDescendingIteration() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{4, 3, 2, 1};
-        int index = 0;
-        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
-            assertThat(iter.next().getKey(), is(expected[index++]));
-        }
-        assertThat(index, is(4));
-    }
-
-    @Test
-    public void testDescendingIterationRemove1() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{4, 3, 2};
-        int index = 0;
-        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
-            if (index == 1) {
-                map.remove(1);
-            }
-            assertThat(iter.next().getKey(), is(expected[index++]));
-        }
-        assertThat(index, is(3));
-        assertThat(map.size(), is(3));
-    }
-
-    @Test
-    public void testDescendingIterationRemove2() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{3, 2, 1};
-        int index = 0;
-        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
-            if (index == 0) {
-                map.remove(4);
-            }
-            assertThat(iter.next().getKey(), is(expected[index++]));
-        }
-        assertThat(index, is(3));
-        assertThat(map.size(), is(3));
-    }
-
-    @Test
-    public void testDescendingIterationRemove3() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{4, 1};
-        int index = 0;
-        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
-            if (index == 1) {
-                map.remove(3);
-                map.remove(2);
-            }
-            assertThat(iter.next().getKey(), is(expected[index++]));
-        }
-        assertThat(index, is(2));
-        assertThat(map.size(), is(2));
-    }
-
-    @Test
-    public void testDescendingIterationAddition() {
-        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
-        int[] expected = new int[]{4, 3, 2, 1};
-        int index = 0;
-        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
-            if (index == 0) {
-                map.putIfAbsent(5, false);
-            }
-            assertThat(iter.next().getKey(), is(expected[index++]));
-        }
-        assertThat(index, is(4));
-        assertThat(map.size(), is(5));
-    }
-
-    @Test
-    public void testDescendingIteratorEmpty() {
-        SafeIterableMap<Integer, Boolean> map = mapOf();
-        Iterator<Entry<Integer, Boolean>> iterator = map.descendingIterator();
-        assertThat(iterator.hasNext(), is(false));
-    }
-
-    @Test
-    public void testIteratorEmpty() {
-        SafeIterableMap<Integer, Boolean> map = mapOf();
-        Iterator<Entry<Integer, Boolean>> iterator = map.iterator();
-        assertThat(iterator.hasNext(), is(false));
-    }
-
-    @Test
-    public void testIteratorWithAdditionEmpty() {
-        SafeIterableMap<Integer, Boolean> map = mapOf();
-        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
-        assertThat(iterator.hasNext(), is(false));
-    }
-
-    @Test
-    public void testEldest() {
-        SafeIterableMap<Integer, Boolean> map = mapOf();
-        assertThat(map.eldest(), nullValue());
-        map.putIfAbsent(1, false);
-        assertThat(map.eldest().getKey(), is(1));
-        map.putIfAbsent(2, false);
-        assertThat(map.eldest().getKey(), is(1));
-        map.remove(1);
-        assertThat(map.eldest().getKey(), is(2));
-        map.remove(2);
-        assertThat(map.eldest(), nullValue());
-    }
-
-    @Test
-    public void testNewest() {
-        SafeIterableMap<Integer, Boolean> map = mapOf();
-        assertThat(map.newest(), nullValue());
-        map.putIfAbsent(1, false);
-        assertThat(map.newest().getKey(), is(1));
-        map.putIfAbsent(2, false);
-        assertThat(map.newest().getKey(), is(2));
-        map.remove(2);
-        assertThat(map.eldest().getKey(), is(1));
-        map.remove(1);
-        assertThat(map.newest(), nullValue());
-    }
-
-
-    // for most operations we don't care about values, so we create map from key to true
-    @SafeVarargs
-    private static <K> SafeIterableMap<K, Boolean> mapOf(K... keys) {
-        SafeIterableMap<K, Boolean> map = new SafeIterableMap<>();
-        for (K key : keys) {
-            map.putIfAbsent(key, true);
-        }
-        return map;
-    }
-
-    @SafeVarargs
-    private static <K> MapBuilder<K> from(K... keys) {
-        return new MapBuilder<>(keys);
-    }
-
-    private static class MapBuilder<K> {
-        final K[] mKeys;
-
-        MapBuilder(K[] keys) {
-            this.mKeys = keys;
-        }
-
-        @SafeVarargs
-        public final <V> SafeIterableMap<K, V> to(V... values) {
-            assertThat("Failed to build Map", mKeys.length, is(values.length));
-            SafeIterableMap<K, V> map = new SafeIterableMap<>();
-            for (int i = 0; i < mKeys.length; i++) {
-                map.putIfAbsent(mKeys[i], values[i]);
-            }
-            return map;
-        }
-    }
-}
-
-
diff --git a/app-toolkit/common/src/test/java/androidx/collection/FastSafeIterableMapTest.java b/app-toolkit/common/src/test/java/androidx/collection/FastSafeIterableMapTest.java
new file mode 100644
index 0000000..9b392e2
--- /dev/null
+++ b/app-toolkit/common/src/test/java/androidx/collection/FastSafeIterableMapTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 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 androidx.collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import androidx.arch.core.internal.FastSafeIterableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FastSafeIterableMapTest {
+    @Test
+    public void testCeil() {
+        FastSafeIterableMap<Integer, Boolean> map = new FastSafeIterableMap<>();
+        assertThat(map.ceil(1), nullValue());
+        map.putIfAbsent(1, false);
+        assertThat(map.ceil(1), nullValue());
+        map.putIfAbsent(2, false);
+        assertThat(map.ceil(2).getKey(), is(1));
+        map.remove(1);
+        assertThat(map.ceil(2), nullValue());
+    }
+
+    @Test
+    public void testPut() {
+        FastSafeIterableMap<Integer, Integer> map = new FastSafeIterableMap<>();
+        map.putIfAbsent(10, 20);
+        map.putIfAbsent(20, 40);
+        map.putIfAbsent(30, 60);
+        assertThat(map.putIfAbsent(5, 10), is((Integer) null));
+        assertThat(map.putIfAbsent(10, 30), is(20));
+    }
+
+    @Test
+    public void testContains() {
+        FastSafeIterableMap<Integer, Integer> map = new FastSafeIterableMap<>();
+        map.putIfAbsent(10, 20);
+        map.putIfAbsent(20, 40);
+        map.putIfAbsent(30, 60);
+        assertThat(map.contains(10), is(true));
+        assertThat(map.contains(11), is(false));
+        assertThat(new FastSafeIterableMap<Integer, Integer>().contains(0), is(false));
+    }
+
+
+    @Test
+    public void testRemove() {
+        FastSafeIterableMap<Integer, Integer> map = new FastSafeIterableMap<>();
+        map.putIfAbsent(10, 20);
+        map.putIfAbsent(20, 40);
+        assertThat(map.contains(10), is(true));
+        assertThat(map.contains(20), is(true));
+        assertThat(map.remove(10), is(20));
+        assertThat(map.contains(10), is(false));
+        assertThat(map.putIfAbsent(10, 30), nullValue());
+        assertThat(map.putIfAbsent(10, 40), is(30));
+    }
+}
diff --git a/app-toolkit/common/src/test/java/androidx/collection/SafeIterableMapTest.java b/app-toolkit/common/src/test/java/androidx/collection/SafeIterableMapTest.java
new file mode 100644
index 0000000..f425f6b
--- /dev/null
+++ b/app-toolkit/common/src/test/java/androidx/collection/SafeIterableMapTest.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright 2018 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 androidx.collection;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import androidx.arch.core.internal.SafeIterableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+@RunWith(JUnit4.class)
+public class SafeIterableMapTest {
+
+    @Test
+    public void testToString() {
+        SafeIterableMap<Integer, String> map = from(1, 2, 3, 4).to("a", "b", "c", "d");
+        assertThat(map.toString(), is("[1=a, 2=b, 3=c, 4=d]"));
+    }
+
+    @Test
+    public void testEmptyToString() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        assertThat(map.toString(), is("[]"));
+    }
+
+    @Test
+    public void testOneElementToString() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1);
+        assertThat(map.toString(), is("[1=true]"));
+    }
+
+
+    @Test
+    public void testEquality1() {
+        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map1.equals(map2), is(true));
+    }
+
+    @Test
+    public void testEquality2() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        //noinspection ObjectEqualsNull
+        assertThat(map.equals(null), is(false));
+    }
+
+    @Test
+    public void testEquality3() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        //noinspection EqualsBetweenInconvertibleTypes
+        assertThat(map.equals(new ArrayList<>()), is(false));
+    }
+
+    @Test
+    public void testEquality4() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.equals(new SafeIterableMap<Integer, Boolean>()), is(false));
+    }
+
+    @Test
+    public void testEquality5() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
+        SafeIterableMap<Integer, Boolean> map2 = mapOf(1);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+    @Test
+    public void testEquality6() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
+        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 2, 3, 5);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+    @Test
+    public void testEquality7() {
+        SafeIterableMap<Integer, Integer> map1 = from(1, 2, 3, 4).to(1, 2, 3, 4);
+        SafeIterableMap<Integer, Integer> map2 = from(1, 2, 3, 4).to(1, 2, 3, 5);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+
+    @Test
+    public void testEquality8() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf();
+        SafeIterableMap<Integer, Boolean> map2 = mapOf();
+        assertThat(map1.equals(map2), is(true));
+    }
+
+    @Test
+    public void testEqualityRespectsOrder() {
+        SafeIterableMap<Integer, Boolean> map1 = mapOf(1, 2, 3, 4);
+        SafeIterableMap<Integer, Boolean> map2 = mapOf(1, 3, 2, 4);
+        assertThat(map1.equals(map2), is(false));
+    }
+
+    @Test
+    public void testPut() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map.putIfAbsent(5, 10), is((Integer) null));
+        assertThat(map, is(from(1, 2, 3, 4, 5).to(10, 20, 30, 40, 10)));
+    }
+
+    @Test
+    public void testAddExisted() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 261, 40);
+        assertThat(map.putIfAbsent(3, 239), is(261));
+        assertThat(map, is(from(1, 2, 3, 4).to(10, 20, 261, 40)));
+    }
+
+    @Test
+    public void testRemoveLast() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map.remove(4), is(40));
+        assertThat(map, is(from(1, 2, 3).to(10, 20, 30)));
+    }
+
+    @Test
+    public void testRemoveFirst() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.remove(1), is(true));
+        assertThat(map, is(mapOf(2, 3, 4)));
+    }
+
+    @Test
+    public void testRemoveMiddle() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        assertThat(map.remove(2), is(20));
+        assertThat(map.remove(3), is(30));
+        assertThat(map, is(from(1, 4).to(10, 40)));
+    }
+
+    @Test
+    public void testRemoveNotExisted() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.remove(5), is((Boolean) null));
+        assertThat(map, is(mapOf(1, 2, 3, 4)));
+    }
+
+    @Test
+    public void testRemoveSole() {
+        SafeIterableMap<Integer, Integer> map = from(1).to(261);
+        assertThat(map.remove(1), is(261));
+        assertThat(map, is(new SafeIterableMap<Integer, Integer>()));
+    }
+
+    @Test
+    public void testRemoveDuringIteration1() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        int index = 0;
+        int[] expected = new int[]{1, 4};
+        for (Entry<Integer, Integer> i : map) {
+            assertThat(i.getKey(), is(expected[index++]));
+            if (index == 1) {
+                assertThat(map.remove(2), is(20));
+                assertThat(map.remove(3), is(30));
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveDuringIteration2() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2).to(10, 20);
+        Iterator<Entry<Integer, Integer>> iter = map.iterator();
+        assertThat(map.remove(2), is(20));
+        assertThat(map.remove(1), is(10));
+        assertThat(iter.hasNext(), is(false));
+    }
+
+    @Test
+    public void testRemoveDuringIteration3() {
+        SafeIterableMap<Integer, Integer> map = from(1, 2, 3, 4).to(10, 20, 30, 40);
+        int index = 0;
+        Iterator<Entry<Integer, Integer>> iter = map.iterator();
+        assertThat(map.remove(1), is(10));
+        assertThat(map.remove(2), is(20));
+        int[] expected = new int[]{3, 4};
+        while (iter.hasNext()) {
+            assertThat(iter.next().getKey(), is(expected[index++]));
+        }
+    }
+
+    @Test
+    public void testRemoveDuringIteration4() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2);
+        int[] expected = new int[]{1, 2};
+        int index = 0;
+        for (Entry<Integer, Boolean> entry : map) {
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.remove(1);
+            }
+        }
+        assertThat(index, is(2));
+    }
+
+    @Test
+    public void testAdditionDuringIteration() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{1, 2, 3, 4};
+        int index = 0;
+        for (Entry<Integer, Boolean> entry : map) {
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.putIfAbsent(5, true);
+            }
+        }
+    }
+
+    @Test
+    public void testReAdditionDuringIteration() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{1, 2, 4};
+        int index = 0;
+        for (Entry<Integer, Boolean> entry : map) {
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.remove(3);
+                map.putIfAbsent(3, true);
+            }
+        }
+    }
+
+    @Test
+    public void testSize() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        assertThat(map.size(), is(4));
+        map.putIfAbsent(5, true);
+        map.putIfAbsent(6, true);
+        assertThat(map.size(), is(6));
+        map.remove(5);
+        map.remove(5);
+        assertThat(map.size(), is(5));
+        map.remove(1);
+        map.remove(2);
+        map.remove(4);
+        map.remove(3);
+        map.remove(6);
+        assertThat(map.size(), is(0));
+        map.putIfAbsent(4, true);
+        assertThat(map.size(), is(1));
+        assertThat(mapOf().size(), is(0));
+    }
+
+    @Test
+    public void testIteratorWithAdditions1() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{1, 2, 3, 5};
+        int index = 0;
+        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
+        while (iterator.hasNext()) {
+            Entry<Integer, Boolean> entry = iterator.next();
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 3) {
+                map.remove(4);
+                map.putIfAbsent(5, true);
+            }
+        }
+    }
+
+    @Test
+    public void testIteratorWithAdditions2() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1);
+        int[] expected = new int[]{1, 2, 3};
+        int index = 0;
+        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
+        while (iterator.hasNext()) {
+            Entry<Integer, Boolean> entry = iterator.next();
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.putIfAbsent(2, true);
+                map.putIfAbsent(3, true);
+            }
+        }
+        assertThat(index, is(3));
+    }
+
+
+    @Test
+    public void testIteratorWithAdditions3() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3);
+        int[] expected = new int[]{1};
+        int index = 0;
+        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
+        while (iterator.hasNext()) {
+            Entry<Integer, Boolean> entry = iterator.next();
+            assertThat(entry.getKey(), is(expected[index++]));
+            map.remove(2);
+            map.remove(3);
+        }
+        assertThat(index, is(1));
+    }
+
+    @Test
+    public void testIteratorWithAdditions4() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        int[] expected = new int[]{1, 2, 3};
+        int index = 0;
+        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
+        map.putIfAbsent(1, true);
+        while (iterator.hasNext()) {
+            Entry<Integer, Boolean> entry = iterator.next();
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.putIfAbsent(2, false);
+            }
+            if (index == 2) {
+                map.putIfAbsent(3, false);
+            }
+        }
+        assertThat(index, is(3));
+    }
+
+    @Test
+    public void testIteratorWithAddition5() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2);
+        int[] expected = new int[]{1, 2};
+        int index = 0;
+        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
+        while (iterator.hasNext()) {
+            Entry<Integer, Boolean> entry = iterator.next();
+            assertThat(entry.getKey(), is(expected[index++]));
+            if (index == 1) {
+                map.remove(1);
+            }
+        }
+        assertThat(index, is(2));
+    }
+
+    @Test
+    public void testDescendingIteration() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{4, 3, 2, 1};
+        int index = 0;
+        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
+            assertThat(iter.next().getKey(), is(expected[index++]));
+        }
+        assertThat(index, is(4));
+    }
+
+    @Test
+    public void testDescendingIterationRemove1() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{4, 3, 2};
+        int index = 0;
+        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
+            if (index == 1) {
+                map.remove(1);
+            }
+            assertThat(iter.next().getKey(), is(expected[index++]));
+        }
+        assertThat(index, is(3));
+        assertThat(map.size(), is(3));
+    }
+
+    @Test
+    public void testDescendingIterationRemove2() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{3, 2, 1};
+        int index = 0;
+        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
+            if (index == 0) {
+                map.remove(4);
+            }
+            assertThat(iter.next().getKey(), is(expected[index++]));
+        }
+        assertThat(index, is(3));
+        assertThat(map.size(), is(3));
+    }
+
+    @Test
+    public void testDescendingIterationRemove3() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{4, 1};
+        int index = 0;
+        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
+            if (index == 1) {
+                map.remove(3);
+                map.remove(2);
+            }
+            assertThat(iter.next().getKey(), is(expected[index++]));
+        }
+        assertThat(index, is(2));
+        assertThat(map.size(), is(2));
+    }
+
+    @Test
+    public void testDescendingIterationAddition() {
+        SafeIterableMap<Integer, Boolean> map = mapOf(1, 2, 3, 4);
+        int[] expected = new int[]{4, 3, 2, 1};
+        int index = 0;
+        for (Iterator<Entry<Integer, Boolean>> iter = map.descendingIterator(); iter.hasNext(); ) {
+            if (index == 0) {
+                map.putIfAbsent(5, false);
+            }
+            assertThat(iter.next().getKey(), is(expected[index++]));
+        }
+        assertThat(index, is(4));
+        assertThat(map.size(), is(5));
+    }
+
+    @Test
+    public void testDescendingIteratorEmpty() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        Iterator<Entry<Integer, Boolean>> iterator = map.descendingIterator();
+        assertThat(iterator.hasNext(), is(false));
+    }
+
+    @Test
+    public void testIteratorEmpty() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        Iterator<Entry<Integer, Boolean>> iterator = map.iterator();
+        assertThat(iterator.hasNext(), is(false));
+    }
+
+    @Test
+    public void testIteratorWithAdditionEmpty() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        Iterator<Entry<Integer, Boolean>> iterator = map.iteratorWithAdditions();
+        assertThat(iterator.hasNext(), is(false));
+    }
+
+    @Test
+    public void testEldest() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        assertThat(map.eldest(), nullValue());
+        map.putIfAbsent(1, false);
+        assertThat(map.eldest().getKey(), is(1));
+        map.putIfAbsent(2, false);
+        assertThat(map.eldest().getKey(), is(1));
+        map.remove(1);
+        assertThat(map.eldest().getKey(), is(2));
+        map.remove(2);
+        assertThat(map.eldest(), nullValue());
+    }
+
+    @Test
+    public void testNewest() {
+        SafeIterableMap<Integer, Boolean> map = mapOf();
+        assertThat(map.newest(), nullValue());
+        map.putIfAbsent(1, false);
+        assertThat(map.newest().getKey(), is(1));
+        map.putIfAbsent(2, false);
+        assertThat(map.newest().getKey(), is(2));
+        map.remove(2);
+        assertThat(map.eldest().getKey(), is(1));
+        map.remove(1);
+        assertThat(map.newest(), nullValue());
+    }
+
+
+    // for most operations we don't care about values, so we create map from key to true
+    @SafeVarargs
+    private static <K> SafeIterableMap<K, Boolean> mapOf(K... keys) {
+        SafeIterableMap<K, Boolean> map = new SafeIterableMap<>();
+        for (K key : keys) {
+            map.putIfAbsent(key, true);
+        }
+        return map;
+    }
+
+    @SafeVarargs
+    private static <K> MapBuilder<K> from(K... keys) {
+        return new MapBuilder<>(keys);
+    }
+
+    private static class MapBuilder<K> {
+        final K[] mKeys;
+
+        MapBuilder(K[] keys) {
+            this.mKeys = keys;
+        }
+
+        @SafeVarargs
+        public final <V> SafeIterableMap<K, V> to(V... values) {
+            assertThat("Failed to build Map", mKeys.length, is(values.length));
+            SafeIterableMap<K, V> map = new SafeIterableMap<>();
+            for (int i = 0; i < mKeys.length; i++) {
+                map.putIfAbsent(mKeys[i], values[i]);
+            }
+            return map;
+        }
+    }
+}
+
+
diff --git a/app-toolkit/core-testing/api/current.txt b/app-toolkit/core-testing/api/current.txt
index f1d206c..37e16fb 100644
--- a/app-toolkit/core-testing/api/current.txt
+++ b/app-toolkit/core-testing/api/current.txt
@@ -1,4 +1,4 @@
-package android.arch.core.executor.testing {
+package androidx.arch.core.executor.testing {
 
   public class CountingTaskExecutorRule extends org.junit.rules.TestWatcher {
     ctor public CountingTaskExecutorRule();
diff --git a/app-toolkit/core-testing/api/1.0.0.txt b/app-toolkit/core-testing/api_legacy/1.0.0.txt
similarity index 100%
rename from app-toolkit/core-testing/api/1.0.0.txt
rename to app-toolkit/core-testing/api_legacy/1.0.0.txt
diff --git a/app-toolkit/core-testing/api/1.1.0.txt b/app-toolkit/core-testing/api_legacy/1.1.0.txt
similarity index 100%
rename from app-toolkit/core-testing/api/1.1.0.txt
rename to app-toolkit/core-testing/api_legacy/1.1.0.txt
diff --git a/app-toolkit/core-testing/api/1.0.0.txt b/app-toolkit/core-testing/api_legacy/current.txt
similarity index 100%
copy from app-toolkit/core-testing/api/1.0.0.txt
copy to app-toolkit/core-testing/api_legacy/current.txt
diff --git a/app-toolkit/core-testing/build.gradle b/app-toolkit/core-testing/build.gradle
index 946b18d..32d63c8 100644
--- a/app-toolkit/core-testing/build.gradle
+++ b/app-toolkit/core-testing/build.gradle
@@ -24,7 +24,7 @@
 }
 
 dependencies {
-    api(project(":arch:runtime"))
+    api(project(":arch:core-runtime"))
     api(SUPPORT_ANNOTATIONS)
     api(JUNIT)
     api(MOCKITO_CORE, libs.exclude_bytebuddy)
diff --git a/app-toolkit/core-testing/src/androidTest/java/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java b/app-toolkit/core-testing/src/androidTest/java/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
deleted file mode 100644
index a6a5b2e..0000000
--- a/app-toolkit/core-testing/src/androidTest/java/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor.testing;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class CountingTaskExecutorRuleTest {
-    private final Semaphore mOnIdleCount = new Semaphore(0);
-
-    @Rule
-    public CountingTaskExecutorRule mRule = new CountingTaskExecutorRule() {
-        @Override
-        protected void onIdle() {
-            super.onIdle();
-            mOnIdleCount.release(1);
-        }
-    };
-
-    @Test
-    public void initialIdle() {
-        assertThat(mRule.isIdle(), is(true));
-    }
-
-    @Test
-    public void busyIO() throws InterruptedException {
-        LatchRunnable task = runOnIO();
-        singleTaskTest(task);
-    }
-
-    @Test
-    public void busyMain() throws InterruptedException {
-        LatchRunnable task = runOnMain();
-        singleTaskTest(task);
-    }
-
-    @Test
-    public void multipleTasks() throws InterruptedException {
-        List<LatchRunnable> latches = new ArrayList<>(10);
-        for (int i = 0; i < 5; i++) {
-            latches.add(runOnIO());
-            latches.add(runOnMain());
-        }
-        assertNotIdle();
-        for (int i = 0; i < 9; i++) {
-            latches.get(i).start();
-        }
-        for (int i = 0; i < 9; i++) {
-            latches.get(i).await();
-        }
-        assertNotIdle();
-
-        LatchRunnable another = runOnIO();
-        latches.get(9).startAndFinish();
-        assertNotIdle();
-
-        another.startAndFinish();
-        assertBecomeIdle();
-
-        LatchRunnable oneMore = runOnMain();
-
-        assertNotIdle();
-
-        oneMore.startAndFinish();
-        assertBecomeIdle();
-    }
-
-    private void assertNotIdle() throws InterruptedException {
-        assertThat(mOnIdleCount.tryAcquire(300, TimeUnit.MILLISECONDS), is(false));
-        assertThat(mRule.isIdle(), is(false));
-    }
-
-    private void assertBecomeIdle() throws InterruptedException {
-        assertThat(mOnIdleCount.tryAcquire(1, TimeUnit.SECONDS), is(true));
-        assertThat(mRule.isIdle(), is(true));
-    }
-
-    private void singleTaskTest(LatchRunnable task)
-            throws InterruptedException {
-        assertNotIdle();
-        task.startAndFinish();
-        assertBecomeIdle();
-    }
-
-    private LatchRunnable runOnIO() {
-        LatchRunnable latchRunnable = new LatchRunnable();
-        ArchTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
-        return latchRunnable;
-    }
-
-    private LatchRunnable runOnMain() {
-        LatchRunnable latchRunnable = new LatchRunnable();
-        ArchTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
-        return latchRunnable;
-    }
-
-    @Test
-    public void drainFailure() throws InterruptedException {
-        runOnIO();
-        try {
-            mRule.drainTasks(300, TimeUnit.MILLISECONDS);
-            throw new AssertionError("drain should fail");
-        } catch (TimeoutException ignored) {
-        }
-    }
-
-    @Test
-    public void drainSuccess() throws TimeoutException, InterruptedException {
-        final LatchRunnable task = runOnIO();
-        new Thread(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    Thread.sleep(300);
-                } catch (InterruptedException ignored) {
-                }
-                task.start();
-            }
-        }).start();
-        mRule.drainTasks(1, TimeUnit.SECONDS);
-    }
-
-    private static class LatchRunnable implements Runnable {
-        private final CountDownLatch mStart = new CountDownLatch(1);
-        private final CountDownLatch mEnd = new CountDownLatch(1);
-
-        @Override
-        public void run() {
-            try {
-                mStart.await(10, TimeUnit.SECONDS);
-                mEnd.countDown();
-            } catch (InterruptedException e) {
-                throw new AssertionError(e);
-            }
-        }
-
-        void await() throws InterruptedException {
-            mEnd.await(10, TimeUnit.SECONDS);
-        }
-
-        void start() {
-            mStart.countDown();
-        }
-
-        private void startAndFinish() throws InterruptedException {
-            start();
-            await();
-        }
-    }
-}
diff --git a/app-toolkit/core-testing/src/androidTest/java/androidx/arch/core/executor/testing/CountingTaskExecutorRuleTest.java b/app-toolkit/core-testing/src/androidTest/java/androidx/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
new file mode 100644
index 0000000..200e64f
--- /dev/null
+++ b/app-toolkit/core-testing/src/androidTest/java/androidx/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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 androidx.arch.core.executor.testing;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.arch.core.executor.ArchTaskExecutor;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CountingTaskExecutorRuleTest {
+    private final Semaphore mOnIdleCount = new Semaphore(0);
+
+    @Rule
+    public CountingTaskExecutorRule mRule = new CountingTaskExecutorRule() {
+        @Override
+        protected void onIdle() {
+            super.onIdle();
+            mOnIdleCount.release(1);
+        }
+    };
+
+    @Test
+    public void initialIdle() {
+        assertThat(mRule.isIdle(), is(true));
+    }
+
+    @Test
+    public void busyIO() throws InterruptedException {
+        LatchRunnable task = runOnIO();
+        singleTaskTest(task);
+    }
+
+    @Test
+    public void busyMain() throws InterruptedException {
+        LatchRunnable task = runOnMain();
+        singleTaskTest(task);
+    }
+
+    @Test
+    public void multipleTasks() throws InterruptedException {
+        List<LatchRunnable> latches = new ArrayList<>(10);
+        for (int i = 0; i < 5; i++) {
+            latches.add(runOnIO());
+            latches.add(runOnMain());
+        }
+        assertNotIdle();
+        for (int i = 0; i < 9; i++) {
+            latches.get(i).start();
+        }
+        for (int i = 0; i < 9; i++) {
+            latches.get(i).await();
+        }
+        assertNotIdle();
+
+        LatchRunnable another = runOnIO();
+        latches.get(9).startAndFinish();
+        assertNotIdle();
+
+        another.startAndFinish();
+        assertBecomeIdle();
+
+        LatchRunnable oneMore = runOnMain();
+
+        assertNotIdle();
+
+        oneMore.startAndFinish();
+        assertBecomeIdle();
+    }
+
+    private void assertNotIdle() throws InterruptedException {
+        assertThat(mOnIdleCount.tryAcquire(300, TimeUnit.MILLISECONDS), is(false));
+        assertThat(mRule.isIdle(), is(false));
+    }
+
+    private void assertBecomeIdle() throws InterruptedException {
+        assertThat(mOnIdleCount.tryAcquire(1, TimeUnit.SECONDS), is(true));
+        assertThat(mRule.isIdle(), is(true));
+    }
+
+    private void singleTaskTest(LatchRunnable task)
+            throws InterruptedException {
+        assertNotIdle();
+        task.startAndFinish();
+        assertBecomeIdle();
+    }
+
+    private LatchRunnable runOnIO() {
+        LatchRunnable latchRunnable = new LatchRunnable();
+        ArchTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
+        return latchRunnable;
+    }
+
+    private LatchRunnable runOnMain() {
+        LatchRunnable latchRunnable = new LatchRunnable();
+        ArchTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
+        return latchRunnable;
+    }
+
+    @Test
+    public void drainFailure() throws InterruptedException {
+        runOnIO();
+        try {
+            mRule.drainTasks(300, TimeUnit.MILLISECONDS);
+            throw new AssertionError("drain should fail");
+        } catch (TimeoutException ignored) {
+        }
+    }
+
+    @Test
+    public void drainSuccess() throws TimeoutException, InterruptedException {
+        final LatchRunnable task = runOnIO();
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(300);
+                } catch (InterruptedException ignored) {
+                }
+                task.start();
+            }
+        }).start();
+        mRule.drainTasks(1, TimeUnit.SECONDS);
+    }
+
+    private static class LatchRunnable implements Runnable {
+        private final CountDownLatch mStart = new CountDownLatch(1);
+        private final CountDownLatch mEnd = new CountDownLatch(1);
+
+        @Override
+        public void run() {
+            try {
+                mStart.await(10, TimeUnit.SECONDS);
+                mEnd.countDown();
+            } catch (InterruptedException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        void await() throws InterruptedException {
+            mEnd.await(10, TimeUnit.SECONDS);
+        }
+
+        void start() {
+            mStart.countDown();
+        }
+
+        private void startAndFinish() throws InterruptedException {
+            start();
+            await();
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/AndroidManifest.xml b/app-toolkit/core-testing/src/main/AndroidManifest.xml
index d169e00..f5cee7e 100644
--- a/app-toolkit/core-testing/src/main/AndroidManifest.xml
+++ b/app-toolkit/core-testing/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.core.testing">
+          package="androidx.arch.core.testing">
 </manifest>
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/JunitTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/JunitTaskExecutorRule.java
deleted file mode 100644
index c3366f3..0000000
--- a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/JunitTaskExecutorRule.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor;
-
-import android.support.annotation.RestrictTo;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.MultipleFailureException;
-import org.junit.runners.model.Statement;
-import org.mockito.Mockito;
-
-import java.util.List;
-
-/**
- * A JUnit rule that swaps the task executor with a more controllable one.
- * Once we have the TaskExecutor API, we should consider making this public (via some test package).
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class JunitTaskExecutorRule implements TestRule {
-    private final TaskExecutorWithFakeMainThread mTaskExecutor;
-
-    public JunitTaskExecutorRule(int ioThreadCount, boolean spyOnExecutor) {
-        if (spyOnExecutor) {
-            mTaskExecutor = Mockito.spy(new TaskExecutorWithFakeMainThread(ioThreadCount));
-        } else {
-            mTaskExecutor = new TaskExecutorWithFakeMainThread(ioThreadCount);
-        }
-
-    }
-
-    private void beforeStart() {
-        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
-    }
-
-    private void afterFinished() {
-        ArchTaskExecutor.getInstance().setDelegate(null);
-    }
-
-    public TaskExecutor getTaskExecutor() {
-        return mTaskExecutor;
-    }
-
-    /**
-     * Awaits while all currently posted tasks will be finished
-     *
-     * @param seconds timeout in seconds
-     */
-    public void drainTasks(int seconds) throws InterruptedException {
-        mTaskExecutor.drainTasks(seconds);
-    }
-
-    @Override
-    public Statement apply(final Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                beforeStart();
-                try {
-                    base.evaluate();
-                    finishExecutors();
-                } catch (Throwable t) {
-                    throw new RuntimeException(t);
-                } finally {
-                    afterFinished();
-                }
-            }
-        };
-    }
-
-    private void finishExecutors() throws InterruptedException, MultipleFailureException {
-        mTaskExecutor.shutdown(10);
-        final List<Throwable> errors = mTaskExecutor.getErrors();
-        if (!errors.isEmpty()) {
-            throw new MultipleFailureException(errors);
-        }
-    }
-}
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/TaskExecutorWithFakeMainThread.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/TaskExecutorWithFakeMainThread.java
deleted file mode 100644
index af0aca4..0000000
--- a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/TaskExecutorWithFakeMainThread.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A TaskExecutor that has a real thread for main thread operations and can wait for execution etc.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class TaskExecutorWithFakeMainThread extends TaskExecutor {
-    private List<Throwable> mCaughtExceptions = Collections.synchronizedList(new ArrayList
-            <Throwable>());
-
-    private ExecutorService mIOService;
-
-    private Thread mMainThread;
-    private final int mIOThreadCount;
-
-    private ExecutorService mMainThreadService =
-            Executors.newSingleThreadExecutor(new ThreadFactory() {
-                @Override
-                public Thread newThread(@NonNull final Runnable r) {
-                    mMainThread = new LoggingThread(r);
-                    return mMainThread;
-                }
-            });
-
-    public TaskExecutorWithFakeMainThread(int ioThreadCount) {
-        mIOThreadCount = ioThreadCount;
-        mIOService = Executors.newFixedThreadPool(ioThreadCount, new ThreadFactory() {
-            @Override
-            public Thread newThread(@NonNull Runnable r) {
-                return new LoggingThread(r);
-            }
-        });
-    }
-
-    @Override
-    public void executeOnDiskIO(Runnable runnable) {
-        mIOService.execute(runnable);
-    }
-
-    @Override
-    public void postToMainThread(Runnable runnable) {
-        // Tasks in SingleThreadExecutor are guaranteed to execute sequentially,
-        // and no more than one task will be active at any given time.
-        // So if we call this method from the main thread, new task will be scheduled,
-        // which is equivalent to post.
-        mMainThreadService.execute(runnable);
-    }
-
-    @Override
-    public boolean isMainThread() {
-        return Thread.currentThread() == mMainThread;
-    }
-
-    List<Throwable> getErrors() {
-        return mCaughtExceptions;
-    }
-
-    @SuppressWarnings("SameParameterValue")
-    void shutdown(int timeoutInSeconds) throws InterruptedException {
-        mMainThreadService.shutdown();
-        mIOService.shutdown();
-        mMainThreadService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS);
-        mIOService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS);
-    }
-
-    /**
-     * Drains tasks at the given time limit
-     * @param seconds Number of seconds to wait
-     * @throws InterruptedException
-     */
-    public void drainTasks(int seconds) throws InterruptedException {
-        if (isMainThread()) {
-            throw new IllegalStateException();
-        }
-        final CountDownLatch enterLatch = new CountDownLatch(mIOThreadCount);
-        final CountDownLatch exitLatch = new CountDownLatch(1);
-        for (int i = 0; i < mIOThreadCount; i++) {
-            executeOnDiskIO(new Runnable() {
-                @Override
-                public void run() {
-                    enterLatch.countDown();
-                    try {
-                        exitLatch.await();
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException(e);
-                    }
-                }
-            });
-        }
-
-        final CountDownLatch mainLatch = new CountDownLatch(1);
-        postToMainThread(new Runnable() {
-            @Override
-            public void run() {
-                mainLatch.countDown();
-            }
-        });
-        if (!enterLatch.await(seconds, TimeUnit.SECONDS)) {
-            throw new AssertionError("Could not drain IO tasks in " + seconds
-                    + " seconds");
-        }
-        exitLatch.countDown();
-        if (!mainLatch.await(seconds, TimeUnit.SECONDS)) {
-            throw new AssertionError("Could not drain UI tasks in " + seconds
-                    + " seconds");
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    class LoggingThread extends Thread {
-        LoggingThread(final Runnable target) {
-            super(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        target.run();
-                    } catch (Throwable t) {
-                        mCaughtExceptions.add(t);
-                    }
-                }
-            });
-        }
-    }
-}
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/CountingTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/CountingTaskExecutorRule.java
deleted file mode 100644
index 77133d5..0000000
--- a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/CountingTaskExecutorRule.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor.testing;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.DefaultTaskExecutor;
-import android.os.SystemClock;
-
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
- * different one which counts the tasks as they are start and finish.
- * <p>
- * You can use this rule for your host side tests that use Architecture Components.
- */
-public class CountingTaskExecutorRule extends TestWatcher {
-    private final Object mCountLock = new Object();
-    private int mTaskCount = 0;
-
-    @Override
-    protected void starting(Description description) {
-        super.starting(description);
-        ArchTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
-            @Override
-            public void executeOnDiskIO(Runnable runnable) {
-                super.executeOnDiskIO(new CountingRunnable(runnable));
-            }
-
-            @Override
-            public void postToMainThread(Runnable runnable) {
-                super.postToMainThread(new CountingRunnable(runnable));
-            }
-        });
-    }
-
-    @Override
-    protected void finished(Description description) {
-        super.finished(description);
-        ArchTaskExecutor.getInstance().setDelegate(null);
-    }
-
-    private void increment() {
-        synchronized (mCountLock) {
-            mTaskCount++;
-        }
-    }
-
-    private void decrement() {
-        synchronized (mCountLock) {
-            mTaskCount--;
-            if (mTaskCount == 0) {
-                onIdle();
-                mCountLock.notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Called when the number of awaiting tasks reaches to 0.
-     *
-     * @see #isIdle()
-     */
-    protected void onIdle() {
-
-    }
-
-    /**
-     * Returns false if there are tasks waiting to be executed, true otherwise.
-     *
-     * @return False if there are tasks waiting to be executed, true otherwise.
-     *
-     * @see #onIdle()
-     */
-    public boolean isIdle() {
-        synchronized (mCountLock) {
-            return mTaskCount == 0;
-        }
-    }
-
-    /**
-     * Waits until all active tasks are finished.
-     *
-     * @param time The duration to wait
-     * @param timeUnit The time unit for the {@code time} parameter
-     *
-     * @throws InterruptedException If thread is interrupted while waiting
-     * @throws TimeoutException If tasks cannot be drained at the given time
-     */
-    public void drainTasks(int time, TimeUnit timeUnit)
-            throws InterruptedException, TimeoutException {
-        long end = SystemClock.uptimeMillis() + timeUnit.toMillis(time);
-        synchronized (mCountLock) {
-            while (mTaskCount != 0) {
-                long now = SystemClock.uptimeMillis();
-                long remaining = end - now;
-                if (remaining > 0) {
-                    mCountLock.wait(remaining);
-                } else {
-                    throw new TimeoutException("could not drain tasks");
-                }
-            }
-        }
-    }
-
-    class CountingRunnable implements Runnable {
-        final Runnable mWrapped;
-
-        CountingRunnable(Runnable wrapped) {
-            mWrapped = wrapped;
-            increment();
-        }
-
-        @Override
-        public void run() {
-            try {
-                mWrapped.run();
-            } finally {
-                decrement();
-            }
-        }
-    }
-}
diff --git a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/InstantTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/InstantTaskExecutorRule.java
deleted file mode 100644
index f88a3e3..0000000
--- a/app-toolkit/core-testing/src/main/java/android/arch/core/executor/testing/InstantTaskExecutorRule.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor.testing;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
-
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-/**
- * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
- * different one which executes each task synchronously.
- * <p>
- * You can use this rule for your host side tests that use Architecture Components.
- */
-public class InstantTaskExecutorRule extends TestWatcher {
-    @Override
-    protected void starting(Description description) {
-        super.starting(description);
-        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
-            @Override
-            public void executeOnDiskIO(Runnable runnable) {
-                runnable.run();
-            }
-
-            @Override
-            public void postToMainThread(Runnable runnable) {
-                runnable.run();
-            }
-
-            @Override
-            public boolean isMainThread() {
-                return true;
-            }
-        });
-    }
-
-    @Override
-    protected void finished(Description description) {
-        super.finished(description);
-        ArchTaskExecutor.getInstance().setDelegate(null);
-    }
-}
diff --git a/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/JunitTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/JunitTaskExecutorRule.java
new file mode 100644
index 0000000..b02339d
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/JunitTaskExecutorRule.java
@@ -0,0 +1,94 @@
+/*
+ * 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 androidx.arch.core.executor;
+
+import androidx.annotation.RestrictTo;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+import org.mockito.Mockito;
+
+import java.util.List;
+
+/**
+ * A JUnit rule that swaps the task executor with a more controllable one.
+ * Once we have the TaskExecutor API, we should consider making this public (via some test package).
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class JunitTaskExecutorRule implements TestRule {
+    private final TaskExecutorWithFakeMainThread mTaskExecutor;
+
+    public JunitTaskExecutorRule(int ioThreadCount, boolean spyOnExecutor) {
+        if (spyOnExecutor) {
+            mTaskExecutor = Mockito.spy(new TaskExecutorWithFakeMainThread(ioThreadCount));
+        } else {
+            mTaskExecutor = new TaskExecutorWithFakeMainThread(ioThreadCount);
+        }
+
+    }
+
+    private void beforeStart() {
+        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+    }
+
+    private void afterFinished() {
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    public TaskExecutor getTaskExecutor() {
+        return mTaskExecutor;
+    }
+
+    /**
+     * Awaits while all currently posted tasks will be finished
+     *
+     * @param seconds timeout in seconds
+     */
+    public void drainTasks(int seconds) throws InterruptedException {
+        mTaskExecutor.drainTasks(seconds);
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                beforeStart();
+                try {
+                    base.evaluate();
+                    finishExecutors();
+                } catch (Throwable t) {
+                    throw new RuntimeException(t);
+                } finally {
+                    afterFinished();
+                }
+            }
+        };
+    }
+
+    private void finishExecutors() throws InterruptedException, MultipleFailureException {
+        mTaskExecutor.shutdown(10);
+        final List<Throwable> errors = mTaskExecutor.getErrors();
+        if (!errors.isEmpty()) {
+            throw new MultipleFailureException(errors);
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/TaskExecutorWithFakeMainThread.java b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/TaskExecutorWithFakeMainThread.java
new file mode 100644
index 0000000..a9c1ff6
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/TaskExecutorWithFakeMainThread.java
@@ -0,0 +1,154 @@
+/*
+ * 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 androidx.arch.core.executor;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A TaskExecutor that has a real thread for main thread operations and can wait for execution etc.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class TaskExecutorWithFakeMainThread extends TaskExecutor {
+    private List<Throwable> mCaughtExceptions = Collections.synchronizedList(new ArrayList
+            <Throwable>());
+
+    private ExecutorService mIOService;
+
+    private Thread mMainThread;
+    private final int mIOThreadCount;
+
+    private ExecutorService mMainThreadService =
+            Executors.newSingleThreadExecutor(new ThreadFactory() {
+                @Override
+                public Thread newThread(@NonNull final Runnable r) {
+                    mMainThread = new LoggingThread(r);
+                    return mMainThread;
+                }
+            });
+
+    public TaskExecutorWithFakeMainThread(int ioThreadCount) {
+        mIOThreadCount = ioThreadCount;
+        mIOService = Executors.newFixedThreadPool(ioThreadCount, new ThreadFactory() {
+            @Override
+            public Thread newThread(@NonNull Runnable r) {
+                return new LoggingThread(r);
+            }
+        });
+    }
+
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        mIOService.execute(runnable);
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        // Tasks in SingleThreadExecutor are guaranteed to execute sequentially,
+        // and no more than one task will be active at any given time.
+        // So if we call this method from the main thread, new task will be scheduled,
+        // which is equivalent to post.
+        mMainThreadService.execute(runnable);
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return Thread.currentThread() == mMainThread;
+    }
+
+    List<Throwable> getErrors() {
+        return mCaughtExceptions;
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    void shutdown(int timeoutInSeconds) throws InterruptedException {
+        mMainThreadService.shutdown();
+        mIOService.shutdown();
+        mMainThreadService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS);
+        mIOService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Drains tasks at the given time limit
+     * @param seconds Number of seconds to wait
+     * @throws InterruptedException
+     */
+    public void drainTasks(int seconds) throws InterruptedException {
+        if (isMainThread()) {
+            throw new IllegalStateException();
+        }
+        final CountDownLatch enterLatch = new CountDownLatch(mIOThreadCount);
+        final CountDownLatch exitLatch = new CountDownLatch(1);
+        for (int i = 0; i < mIOThreadCount; i++) {
+            executeOnDiskIO(new Runnable() {
+                @Override
+                public void run() {
+                    enterLatch.countDown();
+                    try {
+                        exitLatch.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            });
+        }
+
+        final CountDownLatch mainLatch = new CountDownLatch(1);
+        postToMainThread(new Runnable() {
+            @Override
+            public void run() {
+                mainLatch.countDown();
+            }
+        });
+        if (!enterLatch.await(seconds, TimeUnit.SECONDS)) {
+            throw new AssertionError("Could not drain IO tasks in " + seconds
+                    + " seconds");
+        }
+        exitLatch.countDown();
+        if (!mainLatch.await(seconds, TimeUnit.SECONDS)) {
+            throw new AssertionError("Could not drain UI tasks in " + seconds
+                    + " seconds");
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    class LoggingThread extends Thread {
+        LoggingThread(final Runnable target) {
+            super(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        target.run();
+                    } catch (Throwable t) {
+                        mCaughtExceptions.add(t);
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java
new file mode 100644
index 0000000..d3c9878
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java
@@ -0,0 +1,142 @@
+/*
+ * 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 androidx.arch.core.executor.testing;
+
+import android.os.SystemClock;
+
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.DefaultTaskExecutor;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
+ * different one which counts the tasks as they are start and finish.
+ * <p>
+ * You can use this rule for your host side tests that use Architecture Components.
+ */
+public class CountingTaskExecutorRule extends TestWatcher {
+    private final Object mCountLock = new Object();
+    private int mTaskCount = 0;
+
+    @Override
+    protected void starting(Description description) {
+        super.starting(description);
+        ArchTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                super.executeOnDiskIO(new CountingRunnable(runnable));
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                super.postToMainThread(new CountingRunnable(runnable));
+            }
+        });
+    }
+
+    @Override
+    protected void finished(Description description) {
+        super.finished(description);
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    private void increment() {
+        synchronized (mCountLock) {
+            mTaskCount++;
+        }
+    }
+
+    private void decrement() {
+        synchronized (mCountLock) {
+            mTaskCount--;
+            if (mTaskCount == 0) {
+                onIdle();
+                mCountLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Called when the number of awaiting tasks reaches to 0.
+     *
+     * @see #isIdle()
+     */
+    protected void onIdle() {
+
+    }
+
+    /**
+     * Returns false if there are tasks waiting to be executed, true otherwise.
+     *
+     * @return False if there are tasks waiting to be executed, true otherwise.
+     *
+     * @see #onIdle()
+     */
+    public boolean isIdle() {
+        synchronized (mCountLock) {
+            return mTaskCount == 0;
+        }
+    }
+
+    /**
+     * Waits until all active tasks are finished.
+     *
+     * @param time The duration to wait
+     * @param timeUnit The time unit for the {@code time} parameter
+     *
+     * @throws InterruptedException If thread is interrupted while waiting
+     * @throws TimeoutException If tasks cannot be drained at the given time
+     */
+    public void drainTasks(int time, TimeUnit timeUnit)
+            throws InterruptedException, TimeoutException {
+        long end = SystemClock.uptimeMillis() + timeUnit.toMillis(time);
+        synchronized (mCountLock) {
+            while (mTaskCount != 0) {
+                long now = SystemClock.uptimeMillis();
+                long remaining = end - now;
+                if (remaining > 0) {
+                    mCountLock.wait(remaining);
+                } else {
+                    throw new TimeoutException("could not drain tasks");
+                }
+            }
+        }
+    }
+
+    class CountingRunnable implements Runnable {
+        final Runnable mWrapped;
+
+        CountingRunnable(Runnable wrapped) {
+            mWrapped = wrapped;
+            increment();
+        }
+
+        @Override
+        public void run() {
+            try {
+                mWrapped.run();
+            } finally {
+                decrement();
+            }
+        }
+    }
+}
diff --git a/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/testing/InstantTaskExecutorRule.java b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/testing/InstantTaskExecutorRule.java
new file mode 100644
index 0000000..8c5d0b5
--- /dev/null
+++ b/app-toolkit/core-testing/src/main/java/androidx/arch/core/executor/testing/InstantTaskExecutorRule.java
@@ -0,0 +1,58 @@
+/*
+ * 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 androidx.arch.core.executor.testing;
+
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
+ * different one which executes each task synchronously.
+ * <p>
+ * You can use this rule for your host side tests that use Architecture Components.
+ */
+public class InstantTaskExecutorRule extends TestWatcher {
+    @Override
+    protected void starting(Description description) {
+        super.starting(description);
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                runnable.run();
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                runnable.run();
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return true;
+            }
+        });
+    }
+
+    @Override
+    protected void finished(Description description) {
+        super.finished(description);
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+}
diff --git a/app-toolkit/core-testing/src/test/java/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java b/app-toolkit/core-testing/src/test/java/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
deleted file mode 100644
index 0fdcbfb..0000000
--- a/app-toolkit/core-testing/src/test/java/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor.testing;
-
-import static org.junit.Assert.assertTrue;
-
-import android.arch.core.executor.ArchTaskExecutor;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(JUnit4.class)
-public class InstantTaskExecutorRuleTest {
-    @Rule
-    public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
-
-    @Test
-    public void executeOnMain() throws ExecutionException, InterruptedException, TimeoutException {
-        final Thread current = Thread.currentThread();
-        FutureTask<Void> check = new FutureTask<>(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                assertTrue(Thread.currentThread() == current);
-                return null;
-            }
-        });
-        ArchTaskExecutor.getInstance().executeOnMainThread(check);
-        check.get(1, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void executeOnIO() throws ExecutionException, InterruptedException, TimeoutException {
-        final Thread current = Thread.currentThread();
-        FutureTask<Void> check = new FutureTask<>(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                assertTrue(Thread.currentThread() == current);
-                return null;
-            }
-        });
-        ArchTaskExecutor.getInstance().executeOnDiskIO(check);
-        check.get(1, TimeUnit.SECONDS);
-    }
-}
diff --git a/app-toolkit/core-testing/src/test/java/androidx/arch/core/executor/testing/InstantTaskExecutorRuleTest.java b/app-toolkit/core-testing/src/test/java/androidx/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
new file mode 100644
index 0000000..ecbcfd5
--- /dev/null
+++ b/app-toolkit/core-testing/src/test/java/androidx/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 androidx.arch.core.executor.testing;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.arch.core.executor.ArchTaskExecutor;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(JUnit4.class)
+public class InstantTaskExecutorRuleTest {
+    @Rule
+    public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
+
+    @Test
+    public void executeOnMain() throws ExecutionException, InterruptedException, TimeoutException {
+        final Thread current = Thread.currentThread();
+        FutureTask<Void> check = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                assertTrue(Thread.currentThread() == current);
+                return null;
+            }
+        });
+        ArchTaskExecutor.getInstance().executeOnMainThread(check);
+        check.get(1, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void executeOnIO() throws ExecutionException, InterruptedException, TimeoutException {
+        final Thread current = Thread.currentThread();
+        FutureTask<Void> check = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                assertTrue(Thread.currentThread() == current);
+                return null;
+            }
+        });
+        ArchTaskExecutor.getInstance().executeOnDiskIO(check);
+        check.get(1, TimeUnit.SECONDS);
+    }
+}
diff --git a/app-toolkit/runtime/api/1.0.0.txt b/app-toolkit/runtime/api_legacy/1.0.0.txt
similarity index 100%
rename from app-toolkit/runtime/api/1.0.0.txt
rename to app-toolkit/runtime/api_legacy/1.0.0.txt
diff --git a/app-toolkit/runtime/api/1.1.0.ignore b/app-toolkit/runtime/api_legacy/1.1.0.ignore
similarity index 100%
rename from app-toolkit/runtime/api/1.1.0.ignore
rename to app-toolkit/runtime/api_legacy/1.1.0.ignore
diff --git a/app-toolkit/runtime/api/1.1.0.txt b/app-toolkit/runtime/api_legacy/1.1.0.txt
similarity index 100%
rename from app-toolkit/runtime/api/1.1.0.txt
rename to app-toolkit/runtime/api_legacy/1.1.0.txt
diff --git a/app-toolkit/runtime/api/current.txt b/app-toolkit/runtime/api_legacy/current.txt
similarity index 100%
rename from app-toolkit/runtime/api/current.txt
rename to app-toolkit/runtime/api_legacy/current.txt
diff --git a/app-toolkit/runtime/build.gradle b/app-toolkit/runtime/build.gradle
index 5c8f2d9..0010920 100644
--- a/app-toolkit/runtime/build.gradle
+++ b/app-toolkit/runtime/build.gradle
@@ -25,7 +25,7 @@
 
 dependencies {
     api(SUPPORT_ANNOTATIONS)
-    api(project(":arch:common"))
+    api(project(":arch:core-common"))
 }
 
 supportLibrary {
diff --git a/app-toolkit/runtime/src/main/AndroidManifest.xml b/app-toolkit/runtime/src/main/AndroidManifest.xml
index 3f40068..e56fa1a 100644
--- a/app-toolkit/runtime/src/main/AndroidManifest.xml
+++ b/app-toolkit/runtime/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.core">
+          package="androidx.arch.core">
 </manifest>
diff --git a/app-toolkit/runtime/src/main/java/android/arch/core/executor/ArchTaskExecutor.java b/app-toolkit/runtime/src/main/java/android/arch/core/executor/ArchTaskExecutor.java
deleted file mode 100644
index 6276ee3..0000000
--- a/app-toolkit/runtime/src/main/java/android/arch/core/executor/ArchTaskExecutor.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.concurrent.Executor;
-
-/**
- * A static class that serves as a central point to execute common tasks.
- * <p>
- *
- * @hide This API is not final.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ArchTaskExecutor extends TaskExecutor {
-    private static volatile ArchTaskExecutor sInstance;
-
-    @NonNull
-    private TaskExecutor mDelegate;
-
-    @NonNull
-    private TaskExecutor mDefaultTaskExecutor;
-
-    @NonNull
-    private static final Executor sMainThreadExecutor = new Executor() {
-        @Override
-        public void execute(Runnable command) {
-            getInstance().postToMainThread(command);
-        }
-    };
-
-    @NonNull
-    private static final Executor sIOThreadExecutor = new Executor() {
-        @Override
-        public void execute(Runnable command) {
-            getInstance().executeOnDiskIO(command);
-        }
-    };
-
-    private ArchTaskExecutor() {
-        mDefaultTaskExecutor = new DefaultTaskExecutor();
-        mDelegate = mDefaultTaskExecutor;
-    }
-
-    /**
-     * Returns an instance of the task executor.
-     *
-     * @return The singleton ArchTaskExecutor.
-     */
-    @NonNull
-    public static ArchTaskExecutor getInstance() {
-        if (sInstance != null) {
-            return sInstance;
-        }
-        synchronized (ArchTaskExecutor.class) {
-            if (sInstance == null) {
-                sInstance = new ArchTaskExecutor();
-            }
-        }
-        return sInstance;
-    }
-
-    /**
-     * Sets a delegate to handle task execution requests.
-     * <p>
-     * If you have a common executor, you can set it as the delegate and App Toolkit components will
-     * use your executors. You may also want to use this for your tests.
-     * <p>
-     * Calling this method with {@code null} sets it to the default TaskExecutor.
-     *
-     * @param taskExecutor The task executor to handle task requests.
-     */
-    public void setDelegate(@Nullable TaskExecutor taskExecutor) {
-        mDelegate = taskExecutor == null ? mDefaultTaskExecutor : taskExecutor;
-    }
-
-    @Override
-    public void executeOnDiskIO(Runnable runnable) {
-        mDelegate.executeOnDiskIO(runnable);
-    }
-
-    @Override
-    public void postToMainThread(Runnable runnable) {
-        mDelegate.postToMainThread(runnable);
-    }
-
-    @NonNull
-    public static Executor getMainThreadExecutor() {
-        return sMainThreadExecutor;
-    }
-
-    @NonNull
-    public static Executor getIOThreadExecutor() {
-        return sIOThreadExecutor;
-    }
-
-    @Override
-    public boolean isMainThread() {
-        return mDelegate.isMainThread();
-    }
-}
diff --git a/app-toolkit/runtime/src/main/java/android/arch/core/executor/DefaultTaskExecutor.java b/app-toolkit/runtime/src/main/java/android/arch/core/executor/DefaultTaskExecutor.java
deleted file mode 100644
index dbb1054..0000000
--- a/app-toolkit/runtime/src/main/java/android/arch/core/executor/DefaultTaskExecutor.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class DefaultTaskExecutor extends TaskExecutor {
-    private final Object mLock = new Object();
-    private ExecutorService mDiskIO = Executors.newFixedThreadPool(2);
-
-    @Nullable
-    private volatile Handler mMainHandler;
-
-    @Override
-    public void executeOnDiskIO(Runnable runnable) {
-        mDiskIO.execute(runnable);
-    }
-
-    @Override
-    public void postToMainThread(Runnable runnable) {
-        if (mMainHandler == null) {
-            synchronized (mLock) {
-                if (mMainHandler == null) {
-                    mMainHandler = new Handler(Looper.getMainLooper());
-                }
-            }
-        }
-        //noinspection ConstantConditions
-        mMainHandler.post(runnable);
-    }
-
-    @Override
-    public boolean isMainThread() {
-        return Looper.getMainLooper().getThread() == Thread.currentThread();
-    }
-}
diff --git a/app-toolkit/runtime/src/main/java/android/arch/core/executor/TaskExecutor.java b/app-toolkit/runtime/src/main/java/android/arch/core/executor/TaskExecutor.java
deleted file mode 100644
index 7175801..0000000
--- a/app-toolkit/runtime/src/main/java/android/arch/core/executor/TaskExecutor.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.core.executor;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
-/**
- * A task executor that can divide tasks into logical groups.
- * <p>
- * It holds a collection a executors for each group of task.
- * <p>
- * TODO: Don't use this from outside, we don't know what the API will look like yet.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class TaskExecutor {
-    /**
-     * Executes the given task in the disk IO thread pool.
-     *
-     * @param runnable The runnable to run in the disk IO thread pool.
-     */
-    public abstract void executeOnDiskIO(@NonNull Runnable runnable);
-
-    /**
-     * Posts the given task to the main thread.
-     *
-     * @param runnable The runnable to run on the main thread.
-     */
-    public abstract void postToMainThread(@NonNull Runnable runnable);
-
-    /**
-     * Executes the given task on the main thread.
-     * <p>
-     * If the current thread is a main thread, immediately runs the given runnable.
-     *
-     * @param runnable The runnable to run on the main thread.
-     */
-    public void executeOnMainThread(@NonNull Runnable runnable) {
-        if (isMainThread()) {
-            runnable.run();
-        } else {
-            postToMainThread(runnable);
-        }
-    }
-
-    /**
-     * Returns true if the current thread is the main thread, false otherwise.
-     *
-     * @return true if we are on the main thread, false otherwise.
-     */
-    public abstract boolean isMainThread();
-}
diff --git a/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/ArchTaskExecutor.java b/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/ArchTaskExecutor.java
new file mode 100644
index 0000000..a38dff8
--- /dev/null
+++ b/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/ArchTaskExecutor.java
@@ -0,0 +1,118 @@
+/*
+ * 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 androidx.arch.core.executor;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A static class that serves as a central point to execute common tasks.
+ * <p>
+ *
+ * @hide This API is not final.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ArchTaskExecutor extends TaskExecutor {
+    private static volatile ArchTaskExecutor sInstance;
+
+    @NonNull
+    private TaskExecutor mDelegate;
+
+    @NonNull
+    private TaskExecutor mDefaultTaskExecutor;
+
+    @NonNull
+    private static final Executor sMainThreadExecutor = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            getInstance().postToMainThread(command);
+        }
+    };
+
+    @NonNull
+    private static final Executor sIOThreadExecutor = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            getInstance().executeOnDiskIO(command);
+        }
+    };
+
+    private ArchTaskExecutor() {
+        mDefaultTaskExecutor = new DefaultTaskExecutor();
+        mDelegate = mDefaultTaskExecutor;
+    }
+
+    /**
+     * Returns an instance of the task executor.
+     *
+     * @return The singleton ArchTaskExecutor.
+     */
+    @NonNull
+    public static ArchTaskExecutor getInstance() {
+        if (sInstance != null) {
+            return sInstance;
+        }
+        synchronized (ArchTaskExecutor.class) {
+            if (sInstance == null) {
+                sInstance = new ArchTaskExecutor();
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Sets a delegate to handle task execution requests.
+     * <p>
+     * If you have a common executor, you can set it as the delegate and App Toolkit components will
+     * use your executors. You may also want to use this for your tests.
+     * <p>
+     * Calling this method with {@code null} sets it to the default TaskExecutor.
+     *
+     * @param taskExecutor The task executor to handle task requests.
+     */
+    public void setDelegate(@Nullable TaskExecutor taskExecutor) {
+        mDelegate = taskExecutor == null ? mDefaultTaskExecutor : taskExecutor;
+    }
+
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        mDelegate.executeOnDiskIO(runnable);
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        mDelegate.postToMainThread(runnable);
+    }
+
+    @NonNull
+    public static Executor getMainThreadExecutor() {
+        return sMainThreadExecutor;
+    }
+
+    @NonNull
+    public static Executor getIOThreadExecutor() {
+        return sIOThreadExecutor;
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return mDelegate.isMainThread();
+    }
+}
diff --git a/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java b/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
new file mode 100644
index 0000000..a942324
--- /dev/null
+++ b/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
@@ -0,0 +1,61 @@
+/*
+ * 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 androidx.arch.core.executor;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class DefaultTaskExecutor extends TaskExecutor {
+    private final Object mLock = new Object();
+    private ExecutorService mDiskIO = Executors.newFixedThreadPool(2);
+
+    @Nullable
+    private volatile Handler mMainHandler;
+
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        mDiskIO.execute(runnable);
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        if (mMainHandler == null) {
+            synchronized (mLock) {
+                if (mMainHandler == null) {
+                    mMainHandler = new Handler(Looper.getMainLooper());
+                }
+            }
+        }
+        //noinspection ConstantConditions
+        mMainHandler.post(runnable);
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return Looper.getMainLooper().getThread() == Thread.currentThread();
+    }
+}
diff --git a/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/TaskExecutor.java b/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/TaskExecutor.java
new file mode 100644
index 0000000..4b3c5fa
--- /dev/null
+++ b/app-toolkit/runtime/src/main/java/androidx/arch/core/executor/TaskExecutor.java
@@ -0,0 +1,67 @@
+/*
+ * 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 androidx.arch.core.executor;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ * A task executor that can divide tasks into logical groups.
+ * <p>
+ * It holds a collection a executors for each group of task.
+ * <p>
+ * TODO: Don't use this from outside, we don't know what the API will look like yet.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class TaskExecutor {
+    /**
+     * Executes the given task in the disk IO thread pool.
+     *
+     * @param runnable The runnable to run in the disk IO thread pool.
+     */
+    public abstract void executeOnDiskIO(@NonNull Runnable runnable);
+
+    /**
+     * Posts the given task to the main thread.
+     *
+     * @param runnable The runnable to run on the main thread.
+     */
+    public abstract void postToMainThread(@NonNull Runnable runnable);
+
+    /**
+     * Executes the given task on the main thread.
+     * <p>
+     * If the current thread is a main thread, immediately runs the given runnable.
+     *
+     * @param runnable The runnable to run on the main thread.
+     */
+    public void executeOnMainThread(@NonNull Runnable runnable) {
+        if (isMainThread()) {
+            runnable.run();
+        } else {
+            postToMainThread(runnable);
+        }
+    }
+
+    /**
+     * Returns true if the current thread is the main thread, false otherwise.
+     *
+     * @return true if we are on the main thread, false otherwise.
+     */
+    public abstract boolean isMainThread();
+}
diff --git a/app-toolkit/settings.gradle b/app-toolkit/settings.gradle
index 9bb671a..b463df7 100644
--- a/app-toolkit/settings.gradle
+++ b/app-toolkit/settings.gradle
@@ -48,33 +48,38 @@
 
 println "support root:${supportRoot}"
 
-includeProject(":arch:common", new File(supportRoot, "app-toolkit/common"))
+includeProject(":arch:core-common", new File(supportRoot, "app-toolkit/common"))
 includeProject(":arch:core-testing", new File(supportRoot, "app-toolkit/core-testing"))
-includeProject(":arch:runtime", new File(supportRoot, "app-toolkit/runtime"))
-includeProject(":lifecycle:common", new File(supportRoot, "lifecycle/common"))
-includeProject(":lifecycle:common-java8", new File(supportRoot, "lifecycle/common-java8"))
-includeProject(":lifecycle:compiler", new File(supportRoot, "lifecycle/compiler"))
-includeProject(":lifecycle:extensions", new File(supportRoot, "lifecycle/extensions"))
+includeProject(":arch:core-runtime", new File(supportRoot, "app-toolkit/runtime"))
 includeProject(":lifecycle:integration-tests:testapp", new File(supportRoot, "lifecycle/integration-tests/testapp"))
-includeProject(":lifecycle:livedata-core", new File(supportRoot, "lifecycle/livedata-core"))
-includeProject(":lifecycle:livedata", new File(supportRoot, "lifecycle/livedata"))
-includeProject(":lifecycle:reactivestreams", new File(supportRoot, "lifecycle/reactivestreams"))
-includeProject(":lifecycle:runtime", new File(supportRoot, "lifecycle/runtime"))
-includeProject(":lifecycle:viewmodel", new File(supportRoot, "lifecycle/viewmodel"))
-includeProject(":paging:common", new File(supportRoot, "paging/common"))
+includeProject(":lifecycle:lifecycle-common", new File(supportRoot, "lifecycle/common"))
+includeProject(":lifecycle:lifecycle-common-java8", new File(supportRoot, "lifecycle/common-java8"))
+includeProject(":lifecycle:lifecycle-compiler", new File(supportRoot, "lifecycle/compiler"))
+includeProject(":lifecycle:lifecycle-extensions", new File(supportRoot, "lifecycle/extensions"))
+includeProject(":lifecycle:lifecycle-livedata-core", new File(supportRoot, "lifecycle/livedata-core"))
+includeProject(":lifecycle:lifecycle-livedata", new File(supportRoot, "lifecycle/livedata"))
+includeProject(":lifecycle:lifecycle-reactivestreams", new File(supportRoot, "lifecycle/reactivestreams"))
+includeProject(":lifecycle:lifecycle-runtime", new File(supportRoot, "lifecycle/runtime"))
+includeProject(":lifecycle:lifecycle-viewmodel", new File(supportRoot, "lifecycle/viewmodel"))
 includeProject(":paging:integration-tests:testapp", new File(supportRoot, "paging/integration-tests/testapp"))
-includeProject(":paging:runtime", new File(supportRoot, "paging/runtime"))
-includeProject(":persistence:db", new File(supportRoot, "persistence/db"))
-includeProject(":persistence:db-framework", new File(supportRoot, "persistence/db-framework"))
-includeProject(":room:common", new File(supportRoot, "room/common"))
-includeProject(":room:compiler", new File(supportRoot, "room/compiler"))
-includeProject(":room:guava", new File(supportRoot, "room/guava"))
+includeProject(":paging:paging-common", new File(supportRoot, "paging/common"))
+includeProject(":paging:paging-runtime", new File(supportRoot, "paging/runtime"))
 includeProject(":room:integration-tests:testapp", new File(supportRoot, "room/integration-tests/testapp"))
 includeProject(":room:integration-tests:kotlintestapp", new File(supportRoot, "room/integration-tests/kotlintestapp"))
-includeProject(":room:migration", new File(supportRoot, "room/migration"))
-includeProject(":room:runtime", new File(supportRoot, "room/runtime"))
-includeProject(":room:rxjava2", new File(supportRoot, "room/rxjava2"))
-includeProject(":room:testing", new File(supportRoot, "room/testing"))
+includeProject(":room:room-common", new File(supportRoot, "room/common"))
+includeProject(":room:room-compiler", new File(supportRoot, "room/compiler"))
+includeProject(":room:room-guava", new File(supportRoot, "room/guava"))
+includeProject(":room:room-migration", new File(supportRoot, "room/migration"))
+includeProject(":room:room-runtime", new File(supportRoot, "room/runtime"))
+includeProject(":room:room-rxjava2", new File(supportRoot, "room/rxjava2"))
+includeProject(":room:room-testing", new File(supportRoot, "room/testing"))
+includeProject(":sqlite:sqlite", new File(supportRoot, "persistence/db"))
+includeProject(":sqlite:sqlite-framework", new File(supportRoot, "persistence/db-framework"))
+
+includeProject(":jetifier-core", new File(supportRoot, "jetifier/jetifier/core"))
+includeProject(":jetifier-gradle-plugin", new File(supportRoot, "jetifier/jetifier/gradle-plugin"))
+includeProject(":jetifier-standalone", new File(supportRoot, "jetifier/jetifier/standalone"))
+includeProject(":jetifier-preprocessor", new File(supportRoot, "jetifier/jetifier/preprocessor"))
 
 /////////////////////////////
 //
diff --git a/browser/build.gradle b/browser/build.gradle
index 9930bbd..4cc89d9 100644
--- a/browser/build.gradle
+++ b/browser/build.gradle
@@ -12,7 +12,6 @@
     api(project(":interpolator"))
     api(project(":collection"))
     api(project(":legacy-support-core-ui"))
-    api(project(":appcompat"))
 
     androidTestImplementation(TEST_RUNNER_TMP, libs.exclude_for_espresso)
     androidTestImplementation(ESPRESSO_CORE_TMP, libs.exclude_for_espresso)
diff --git a/browser/src/main/java/androidx/browser/browseractions/BrowserActionsFallbackMenuDialog.java b/browser/src/main/java/androidx/browser/browseractions/BrowserActionsFallbackMenuDialog.java
index c34c38f..09efc9c 100644
--- a/browser/src/main/java/androidx/browser/browseractions/BrowserActionsFallbackMenuDialog.java
+++ b/browser/src/main/java/androidx/browser/browseractions/BrowserActionsFallbackMenuDialog.java
@@ -39,7 +39,7 @@
     private final View mContentView;
 
     BrowserActionsFallbackMenuDialog(Context context, View contentView) {
-        super(context, androidx.browser.R.style.Theme_AppCompat_Light_Dialog);
+        super(context);
         mContentView = contentView;
     }
 
diff --git a/build.gradle b/build.gradle
index 03b1de7..c731c92 100644
--- a/build.gradle
+++ b/build.gradle
@@ -36,7 +36,6 @@
         classpath build_libs.gradle
         classpath build_libs.lint
         classpath build_libs.jacoco
-        classpath build_libs.jetifier
         classpath build_libs.kotlin.gradle_plugin
     }
 }
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index ac7c501..4e23e62 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -45,7 +45,6 @@
 build_libs.error_prone_gradle = 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13'
 build_libs.jacoco = 'org.jacoco:org.jacoco.core:0.7.8'
 build_libs.jacoco_ant = 'org.jacoco:org.jacoco.ant:0.7.8'
-build_libs.jetifier = 'com.android.support.jetifier:jetifier-gradle-plugin:0.2.0'
 build_libs.kotlin = [
         gradle_plugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:${build_versions.kotlin}"
 ]
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index 50b31a6..2608258 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -39,7 +39,7 @@
 
 libs.support_exclude_config = {
     exclude group: 'android.arch.core'
-    exclude group: 'android.arch.lifecycle'
+    exclude group: 'androidx.lifecycle'
 }
 
 rootProject.ext['libs'] = libs
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index d500eec..df9fc85 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -157,29 +157,22 @@
 }
 
 def configureBuildOnServer() {
-    def preBuildOnServerTask = rootProject.tasks.create("preBuildOnServer")
     def buildOnServerTask = rootProject.tasks.create("buildOnServer")
     rootProject.tasks.whenTaskAdded { task ->
         if ("createArchive".equals(task.name)
                 || "distDocs".equals(task.name)
                 || CheckExternalDependencyLicensesTask.ROOT_TASK_NAME.equals(task.name)) {
-            preBuildOnServerTask.dependsOn task
+            buildOnServerTask.dependsOn task
         }
     }
-
     subprojects {
         project.tasks.whenTaskAdded { task ->
-            if ("assembleErrorProne".equals(task.name)) {
-                preBuildOnServerTask.dependsOn task
-            }
-            if ("assembleAndroidTest".equals(task.name)) {
+            if ("assembleErrorProne".equals(task.name) || "assembleAndroidTest".equals(task.name)) {
                 buildOnServerTask.dependsOn task
-                task.dependsOn preBuildOnServerTask
             }
         }
     }
-    preBuildOnServerTask.dependsOn createJacocoAntUberJarTask()
-    buildOnServerTask.dependsOn preBuildOnServerTask
+    buildOnServerTask.dependsOn createJacocoAntUberJarTask()
 }
 
 def createJacocoAntUberJarTask() {
diff --git a/buildSrc/jetify.gradle b/buildSrc/jetify.gradle
index 42df73f..e81d325 100644
--- a/buildSrc/jetify.gradle
+++ b/buildSrc/jetify.gradle
@@ -25,7 +25,7 @@
     dependsOn ':jetifier-standalone:installDist'
     inputs.file project.tasks['createArchive'].archivePath
 
-    outputs.file "${buildDir}/top-of-tree-m2-repository-dejetified-${project.ext.buildNumber}.zip"
+    outputs.file "${rootProject.ext.distDir}/top-of-tree-m2-repository-dejetified-${project.ext.buildNumber}.zip"
 
-    commandLine ("${jetifierBin}", "-s", "-r", "-outputfile", "${outputs.files.singleFile}",  "-i", "${inputs.files.singleFile}", "-l", "error")
+    commandLine ("${jetifierBin}", "-s", "-r", "-outputfile", "${outputs.files.singleFile}",  "-i", "${inputs.files.singleFile}", "-l", "error", "-rebuildTopOfTree")
 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 9b34de7..4a791cc 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -24,7 +24,7 @@
     const val APPCOMPAT = "androidx.appcompat"
     const val ASYNCLAYOUTINFLATER = "androidx.asynclayoutinflater"
     const val BROWSER = "androidx.browser"
-    const val CAR = "androidx.cardview"
+    const val CAR = "androidx.car"
     const val CARDVIEW = "androidx.cardview"
     const val COLLECTION = "androidx.collection"
     const val CONTENTPAGING = "androidx.contentpaging"
@@ -64,11 +64,11 @@
     const val VIEWPAGER2 = "androidx.viewpager2"
     const val WEAR = "androidx.wear"
     const val WEBKIT = "androidx.webkit"
-    const val ROOM = "android.arch.persistence.room"
-    const val PERSISTENCE = "android.arch.persistence"
-    const val LIFECYCLE = "android.arch.lifecycle"
-    const val ARCH_CORE = "android.arch.core"
-    const val PAGING = "android.arch.paging"
-    const val NAVIGATION = "android.arch.navigation"
+    const val ROOM = "androidx.room"
+    const val PERSISTENCE = "androidx.sqlite"
+    const val LIFECYCLE = "androidx.lifecycle"
+    const val ARCH_CORE = "androidx.arch.core"
+    const val PAGING = "androidx.paging"
+    const val NAVIGATION = "androidx.navigation"
     const val JETIFIER = "com.android.support.jetifier"
 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 90c3ba0..e2993e9 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -28,12 +28,12 @@
     /**
      * Version code for Room
      */
-    val ROOM = Version("1.1.0-alpha3")
+    val ROOM = Version("2.0.0-SNAPSHOT")
 
     /**
      * Version code for Lifecycle extensions (ProcessLifecycleOwner, Fragment support)
      */
-    val LIFECYCLES_EXT = Version("1.2.0-alpha1")
+    val LIFECYCLES_EXT = Version("2.0.0-SNAPSHOT")
 
     /**
      * Version code for Lifecycle LiveData
@@ -48,9 +48,9 @@
     /**
      * Version code for Paging
      */
-    val PAGING = Version("1.0.0-alpha6")
+    val PAGING = Version("2.0.0-SNAPSHOT")
 
-    private val LIFECYCLES = Version("1.1.1-alpha1")
+    private val LIFECYCLES = Version("2.0.0-SNAPSHOT")
 
     /**
      * Version code for Lifecycle libs that are required by the support library
@@ -65,7 +65,7 @@
     /**
      * Version code for shared code of flatfoot
      */
-    val ARCH_CORE = Version("1.1.1-alpha1")
+    val ARCH_CORE = Version("2.0.0-SNAPSHOT")
 
     /**
      * Version code for shared code of flatfoot runtime
diff --git a/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
index 2843aaf..0d3fa3a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
@@ -94,6 +94,8 @@
                 configuration.resolutionStrategy.dependencySubstitution.apply {
                     substitute(module("com.android.support:support-annotations"))
                             .with(project(":annotation"))
+                    substitute(module("androidx.annotation:annotation"))
+                            .with(project(":annotation"))
                 }
             }
         }
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 3c4ae5c..6fea40e 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -35,19 +35,13 @@
 const val KOTLIN_METADATA = "me.eugeniomarletti:kotlin-metadata:1.2.1"
 const val LINT = "com.android.tools.lint:lint:26.0.0"
 const val MOCKITO_CORE = "org.mockito:mockito-core:2.7.6"
-const val MULTIDEX = "com.android.support:multidex:1.0.1"
+const val MULTIDEX = "androidx.multidex:multidex:2.0.0"
 const val NULLAWAY = "com.uber.nullaway:nullaway:0.2.0"
 const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
 const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.0.6"
 const val TEST_RUNNER = "com.android.support.test:runner:1.0.1"
 const val TEST_RULES = "com.android.support.test:rules:1.0.1"
-
-const val ESPRESSO_CONTRIB_TMP = "com.android.temp.support.test.espresso:espresso-contrib:3.0.1"
-const val ESPRESSO_CORE_TMP = "com.android.temp.support.test.espresso:espresso-core:3.0.1"
-const val TEST_RUNNER_TMP = "com.android.temp.support.test:runner:1.0.1"
-const val TEST_RULES_TMP = "com.android.temp.support.test:rules:1.0.1"
-
-
+const val TRUTH = "com.google.truth:truth:0.34"
 /**
  * this Xerial version is newer than we want but we need it to fix
  * https://github.com/xerial/sqlite-jdbc/issues/97
@@ -55,26 +49,24 @@
  */
 const val XERIAL = "org.xerial:sqlite-jdbc:3.20.1"
 
-// Support library dependencies needed for projects that compile against prebuilt versions
-// instead of source directly.
-// NOTE: _27 versions exist for modules that have opted-in to 27, and tests that depend on those
-// modules. Other projects may stick to older versions to avoid forcing users to update.
-private const val SUPPORT_VERSION_27 = "27.1.0"
-const val SUPPORT_RECYCLERVIEW_27 = "com.android.support:recyclerview-v7:$SUPPORT_VERSION_27"
-const val SUPPORT_APPCOMPAT_27 = "com.android.support:appcompat-v7:$SUPPORT_VERSION_27"
+const val ESPRESSO_CONTRIB_TMP = "com.android.temp.support.test.espresso:espresso-contrib:3.0.1"
+const val ESPRESSO_CORE_TMP = "com.android.temp.support.test.espresso:espresso-core:3.0.1"
+const val TEST_RUNNER_TMP = "com.android.temp.support.test:runner:1.0.1"
+const val TEST_RULES_TMP = "com.android.temp.support.test:rules:1.0.1"
 
-private const val SUPPORT_VERSION = "26.1.0"
-const val SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$SUPPORT_VERSION"
-const val SUPPORT_APPCOMPAT = "com.android.support:appcompat-v7:$SUPPORT_VERSION"
-const val SUPPORT_CARDVIEW = "com.android.support:cardview-v7:$SUPPORT_VERSION"
-const val SUPPORT_CORE_UTILS = "com.android.support:support-core-utils:$SUPPORT_VERSION"
-const val SUPPORT_DESIGN = "com.android.support:design:$SUPPORT_VERSION"
-const val SUPPORT_FRAGMENTS = "com.android.support:support-fragment:$SUPPORT_VERSION"
-const val SUPPORT_RECYCLERVIEW = "com.android.support:recyclerview-v7:$SUPPORT_VERSION"
-const val SUPPORT_V4 = "com.android.support:support-v4:$SUPPORT_VERSION"
+// AndroidX libraries
+private const val SUPPORT_VERSION = "1.0.0-SNAPSHOT"
+const val SUPPORT_ANNOTATIONS = "androidx.annotation:annotation:$SUPPORT_VERSION"
+const val SUPPORT_APPCOMPAT = "androidx.appcompat:appcompat:$SUPPORT_VERSION"
+const val SUPPORT_CARDVIEW = "androidx.cardview:cardview:$SUPPORT_VERSION"
+const val SUPPORT_CORE_UTILS = "androidx.legacy:legacy-support-core-utils:$SUPPORT_VERSION"
+const val SUPPORT_DESIGN = "com.google.android.material:material:$SUPPORT_VERSION@aar"
+const val SUPPORT_FRAGMENTS = "androidx.fragment:fragment:$SUPPORT_VERSION"
+const val SUPPORT_RECYCLERVIEW = "androidx.recyclerview:recyclerview:$SUPPORT_VERSION"
+const val SUPPORT_V4 = "androidx.legacy:legacy-support-v4:$SUPPORT_VERSION"
 
 // Arch libraries
-const val ARCH_LIFECYCLE_RUNTIME = "androidx.temp.arch.lifecycle:runtime:2.0.0-alpha1@aar"
-const val ARCH_LIFECYCLE_LIVEDATA_CORE = "androidx.temp.arch.lifecycle:livedata-core:2.0.0-alpha1@aar"
-const val ARCH_LIFECYCLE_VIEWMODEL = "androidx.temp.arch.lifecycle:viewmodel:2.0.0-alpha1@aar"
-const val ARCH_LIFECYCLE_EXTENSIONS = "androidx.temp.arch.lifecycle:extensions:2.0.0-alpha1@aar"
+const val ARCH_LIFECYCLE_RUNTIME = "androidx.lifecycle:lifecycle-runtime:2.0.0-SNAPSHOT@aar"
+const val ARCH_LIFECYCLE_LIVEDATA_CORE = "androidx.lifecycle:lifecycle-livedata-core:2.0.0-SNAPSHOT@aar"
+const val ARCH_LIFECYCLE_VIEWMODEL = "androidx.lifecycle:lifecycle-viewmodel:2.0.0-SNAPSHOT@aar"
+const val ARCH_LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:2.0.0-SNAPSHOT@aar"
diff --git a/buildSrc/src/main/kotlin/androidx/build/license/CheckExternalDependencyLicensesTask.kt b/buildSrc/src/main/kotlin/androidx/build/license/CheckExternalDependencyLicensesTask.kt
index 2dc4e01..426df2d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/license/CheckExternalDependencyLicensesTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/license/CheckExternalDependencyLicensesTask.kt
@@ -50,6 +50,9 @@
                             .filterNot {
                                 it.group?.startsWith("android.arch") == true
                             }
+                            .filterNot {
+                                it.group?.startsWith("androidx") == true
+                            }
                 }
                 .forEach {
                     checkerConfig.dependencies.add(it)
diff --git a/car/build.gradle b/car/build.gradle
index 46621f9..d971e78 100644
--- a/car/build.gradle
+++ b/car/build.gradle
@@ -4,7 +4,6 @@
 
 plugins {
     id("SupportAndroidLibraryPlugin")
-    id("androidx.tools.jetifier")
 }
 
 dependencies {
diff --git a/car/car-stubs/android.car.jar b/car/car-stubs/android.car.jar
index 2c8bf77..b7ee974 100644
--- a/car/car-stubs/android.car.jar
+++ b/car/car-stubs/android.car.jar
Binary files differ
diff --git a/car/res/drawable/car_card_background.xml b/car/res/drawable/car_card_background.xml
index 7caa2ff..1a093e3 100644
--- a/car/res/drawable/car_card_background.xml
+++ b/car/res/drawable/car_card_background.xml
@@ -15,5 +15,5 @@
   ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/car_card"/>
+    <solid android:color="?attr/listItemBackgroundColor"/>
 </shape>
\ No newline at end of file
diff --git a/car/res/drawable/car_card_rounded_background.xml b/car/res/drawable/car_card_rounded_background.xml
index 594705b..240c38b 100644
--- a/car/res/drawable/car_card_rounded_background.xml
+++ b/car/res/drawable/car_card_rounded_background.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/car_card"/>
+    <solid android:color="?attr/listItemBackgroundColor"/>
     <corners
         android:radius="@dimen/car_radius_3"/>
 </shape>
\ No newline at end of file
diff --git a/car/res/drawable/car_card_rounded_bottom_background.xml b/car/res/drawable/car_card_rounded_bottom_background.xml
index 35dba13..611e032 100644
--- a/car/res/drawable/car_card_rounded_bottom_background.xml
+++ b/car/res/drawable/car_card_rounded_bottom_background.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/car_card"/>
+    <solid android:color="?attr/listItemBackgroundColor"/>
     <corners
         android:bottomRightRadius="@dimen/car_radius_3"
         android:bottomLeftRadius="@dimen/car_radius_3"/>
diff --git a/car/res/drawable/car_card_rounded_top_background.xml b/car/res/drawable/car_card_rounded_top_background.xml
index dfb5622..7e85884 100644
--- a/car/res/drawable/car_card_rounded_top_background.xml
+++ b/car/res/drawable/car_card_rounded_top_background.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/car_card"/>
+    <solid android:color="?attr/listItemBackgroundColor"/>
     <corners
         android:topRightRadius="@dimen/car_radius_3"
         android:topLeftRadius="@dimen/car_radius_3"/>
diff --git a/car/res/values/attrs.xml b/car/res/values/attrs.xml
index 3a2959f..0548f79 100644
--- a/car/res/values/attrs.xml
+++ b/car/res/values/attrs.xml
@@ -162,9 +162,6 @@
         <!-- An attribute for specifying a style that modifies the look of a PagedListView. -->
         <attr name="pagedListViewStyle" format="reference"/>
 
-        <!-- An attribute for specifying a style that modifies the look of ListItems. -->
-        <attr name="listItemStyle" format="reference"/>
-
         <!-- ================= -->
         <!-- Dialog Attributes -->
         <!-- ================= -->
diff --git a/car/res/values/themes.xml b/car/res/values/themes.xml
index 86bcd2e..bc3880d 100644
--- a/car/res/values/themes.xml
+++ b/car/res/values/themes.xml
@@ -33,7 +33,9 @@
         <item name="android:colorControlNormal">@color/car_body2</item>
         <item name="carDialogTheme">@style/Theme.Car.Dialog</item>
         <item name="pagedListViewStyle">@style/Widget.Car.List</item>
-        <item name="listItemStyle">@style/Widget.Car.ListItem</item>
+        <item name="listItemBackgroundColor">@color/car_card</item>
+        <item name="listItemTitleTextAppearance">@style/TextAppearance.Car.Body1</item>
+        <item name="listItemBodyTextAppearance">@style/TextAppearance.Car.Body2</item>
     </style>
 
     <!-- Theme for the Car that is a passthrough for the default theme. -->
@@ -46,7 +48,9 @@
         <item name="alertDialogTheme">@style/Theme.Car.Dark.Dialog.Alert</item>
         <item name="carDialogTheme">@style/Theme.Car.Dark.Dialog</item>
         <item name="pagedListViewStyle">@style/Widget.Car.Light.List.LightDivider</item>
-        <item name="listItemStyle">@style/Widget.Car.ListItem.Dark</item>
+        <item name="listItemBackgroundColor">@color/car_card_dark</item>
+        <item name="listItemTitleTextAppearance">@style/TextAppearance.Car.Body1.Light</item>
+        <item name="listItemBodyTextAppearance">@style/TextAppearance.Car.Body2.Light</item>
     </style>
 
     <!-- A Theme for activities that have a drawer affordance. This theme will automatically switch
@@ -146,7 +150,9 @@
          colors will darken during night mode. -->
     <style name="Theme.Car.List" parent="android:Theme">
         <item name="pagedListViewStyle">@style/Widget.Car.List</item>
-        <item name="listItemStyle">@style/Widget.Car.ListItem</item>
+        <item name="listItemBackgroundColor">@color/car_card</item>
+        <item name="listItemTitleTextAppearance">@style/TextAppearance.Car.Body1</item>
+        <item name="listItemBodyTextAppearance">@style/TextAppearance.Car.Body2</item>
     </style>
 
     <!-- A theme for PagedListViews that will have a light scrollbars and light-colored items. The
@@ -158,6 +164,8 @@
     <!-- A theme for PagedListViews that will have a light scrollbars and dark-colored items. The
          colors do not change for night mode. -->
     <style name="Theme.Car.Light.List.DarkItems">
-        <item name="listItemStyle">@style/Widget.Car.ListItem.Dark</item>
+        <item name="listItemBackgroundColor">@color/car_card_dark</item>
+        <item name="listItemTitleTextAppearance">@style/TextAppearance.Car.Body1.Light</item>
+        <item name="listItemBodyTextAppearance">@style/TextAppearance.Car.Body2.Light</item>
     </style>
 </resources>
diff --git a/car/src/main/java/androidx/car/widget/ListItemAdapter.java b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
index c4496cb..4d58488 100644
--- a/car/src/main/java/androidx/car/widget/ListItemAdapter.java
+++ b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
@@ -23,7 +23,6 @@
 import android.os.Bundle;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,6 +32,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Function;
 
+import androidx.annotation.ColorInt;
 import androidx.annotation.IntDef;
 import androidx.annotation.LayoutRes;
 import androidx.annotation.StyleRes;
@@ -96,7 +96,8 @@
             new SparseArray<>();
 
     @ListBackgroundStyle private int mBackgroundStyle;
-    @StyleRes private int mListItemStyle;
+
+    @ColorInt private int mListItemBackgroundColor;
     @StyleRes private int mListItemTitleTextAppearance;
     @StyleRes private int mListItemBodyTextAppearance;
 
@@ -180,9 +181,11 @@
         // When attached to the RecyclerView, update the Context so that this ListItemAdapter can
         // retrieve theme information off that view.
         mContext = recyclerView.getContext();
-        mListItemStyle = getListItemStyle(mContext);
 
-        TypedArray a = mContext.obtainStyledAttributes(mListItemStyle, R.styleable.ListItem);
+        TypedArray a = mContext.getTheme().obtainStyledAttributes(R.styleable.ListItem);
+
+        mListItemBackgroundColor = a.getColor(R.styleable.ListItem_listItemBackgroundColor,
+                mContext.getColor(R.color.car_card));
         mListItemTitleTextAppearance = a.getResourceId(
                 R.styleable.ListItem_listItemTitleTextAppearance,
                 R.style.TextAppearance_Car_Body1);
@@ -211,8 +214,6 @@
      * Creates a view with background set by {@link BackgroundStyle}.
      */
     private ViewGroup createListItemContainer() {
-        TypedArray a = mContext.obtainStyledAttributes(mListItemStyle, R.styleable.ListItem);
-
         ViewGroup container;
         switch (mBackgroundStyle) {
             case BackgroundStyle.NONE:
@@ -220,9 +221,7 @@
                 FrameLayout frameLayout = new FrameLayout(mContext);
                 frameLayout.setLayoutParams(new RecyclerView.LayoutParams(
                         ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
-                frameLayout.setBackgroundColor(a.getColor(
-                        R.styleable.ListItem_listItemBackgroundColor,
-                        mContext.getResources().getColor(R.color.car_card)));
+                frameLayout.setBackgroundColor(mListItemBackgroundColor);
 
                 container = frameLayout;
                 break;
@@ -234,9 +233,7 @@
                         R.dimen.car_padding_3);
                 card.setLayoutParams(cardLayoutParams);
                 card.setRadius(mContext.getResources().getDimensionPixelSize(R.dimen.car_radius_1));
-                card.setCardBackgroundColor(a.getColor(
-                        R.styleable.ListItem_listItemBackgroundColor,
-                        mContext.getResources().getColor(R.color.car_card)));
+                card.setCardBackgroundColor(mListItemBackgroundColor);
 
                 container = card;
                 break;
@@ -244,8 +241,6 @@
                 throw new IllegalArgumentException("Unknown background style. "
                     + "Expected constants in class ListItemAdapter.BackgroundStyle.");
         }
-
-        a.recycle();
         return container;
     }
 
@@ -293,14 +288,4 @@
         return position >= 0 && position < getItemCount()
                 && mItemProvider.get(position).shouldHideDivider();
     }
-
-    /**
-     * Returns the style that has been assigned to {@code listItemStyle} in the
-     * current theme that is inflating this {@code ListItemAdapter}.
-     */
-    private static int getListItemStyle(Context context) {
-        TypedValue outValue = new TypedValue();
-        context.getTheme().resolveAttribute(R.attr.listItemStyle, outValue, true);
-        return outValue.resourceId;
-    }
 }
diff --git a/compat/api/current.txt b/compat/api/current.txt
index faf6744..143e239 100644
--- a/compat/api/current.txt
+++ b/compat/api/current.txt
@@ -946,10 +946,10 @@
 
 package androidx.core.hardware.display {
 
-  public abstract class DisplayManagerCompat {
-    method public abstract android.view.Display getDisplay(int);
-    method public abstract android.view.Display[] getDisplays();
-    method public abstract android.view.Display[] getDisplays(java.lang.String);
+  public final class DisplayManagerCompat {
+    method public android.view.Display getDisplay(int);
+    method public android.view.Display[] getDisplays();
+    method public android.view.Display[] getDisplays(java.lang.String);
     method public static androidx.core.hardware.display.DisplayManagerCompat getInstance(android.content.Context);
     field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
   }
@@ -2489,7 +2489,7 @@
     method public static void scrollListBy(android.widget.ListView, int);
   }
 
-  public class NestedScrollView extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingChild2 androidx.core.view.NestedScrollingParent androidx.core.view.ScrollingView {
+  public class NestedScrollView extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingChild2 androidx.core.view.NestedScrollingParent2 androidx.core.view.ScrollingView {
     ctor public NestedScrollView(android.content.Context);
     ctor public NestedScrollView(android.content.Context, android.util.AttributeSet);
     ctor public NestedScrollView(android.content.Context, android.util.AttributeSet, int);
@@ -2511,6 +2511,11 @@
     method public boolean isFillViewport();
     method public boolean isSmoothScrollingEnabled();
     method public void onAttachedToWindow();
+    method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+    method public void onNestedScroll(android.view.View, int, int, int, int, int);
+    method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+    method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+    method public void onStopNestedScroll(android.view.View, int);
     method public boolean pageScroll(int);
     method public void setFillViewport(boolean);
     method public void setOnScrollChangeListener(androidx.core.widget.NestedScrollView.OnScrollChangeListener);
diff --git a/compat/src/androidTest/AndroidManifest.xml b/compat/src/androidTest/AndroidManifest.xml
index c0e97c2..2cad9bf 100644
--- a/compat/src/androidTest/AndroidManifest.xml
+++ b/compat/src/androidTest/AndroidManifest.xml
@@ -35,6 +35,8 @@
 
         <activity android:name="androidx.core.widget.TextViewTestActivity"/>
 
+        <activity android:name="androidx.core.widget.TestContentViewActivity"/>
+
         <activity android:name="androidx.core.view.VpaActivity"/>
 
         <activity
diff --git a/compat/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingParentTest.java b/compat/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingParentTest.java
new file mode 100644
index 0000000..17f68eb
--- /dev/null
+++ b/compat/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingParentTest.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright 2018 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 androidx.core.widget;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.test.R;
+import androidx.core.view.NestedScrollingChild2;
+import androidx.core.view.NestedScrollingParent2;
+import androidx.core.view.ViewCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * So far these tests only cover {@code NestedScrollView}'s implementation of
+ * {@link NestedScrollingParent2} and the backwards compatibility of {@code NestedScrollView}'s
+ * implementation of {@link androidx.core.view.NestedScrollingParent} for the methods that
+ * {@link NestedScrollingParent2} overloads.
+ */
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NestedScrollViewNestedScrollingParentTest extends
+        BaseInstrumentationTestCase<TestContentViewActivity> {
+
+    private NestedScrollView mNestedScrollView;
+    private NestedScrollingSpyView mParent;
+    private View mChild;
+
+    public NestedScrollViewNestedScrollingParentTest() {
+        super(TestContentViewActivity.class);
+    }
+
+    @Before
+    public void instantiateMembers() {
+        mNestedScrollView = new NestedScrollView(mActivityTestRule.getActivity());
+        mParent = spy(new NestedScrollingSpyView(mActivityTestRule.getActivity()));
+        mChild = new View(mActivityTestRule.getActivity());
+    }
+
+    @Test
+    public void onStartNestedScroll_scrollAxisIncludesVertical_alwaysReturnsTrue() {
+        int vertical = ViewCompat.SCROLL_AXIS_VERTICAL;
+        int both = ViewCompat.SCROLL_AXIS_VERTICAL | ViewCompat.SCROLL_AXIS_HORIZONTAL;
+
+        onStartNestedScrollV1(vertical, true);
+        onStartNestedScrollV1(both, true);
+
+        onStartNestedScrollV2(vertical, true);
+        onStartNestedScrollV2(both, true);
+    }
+
+    @Test
+    public void onStartNestedScroll_scrollAxisExcludesVertical_alwaysReturnsFalse() {
+        int horizontal = ViewCompat.SCROLL_AXIS_HORIZONTAL;
+        int neither = ViewCompat.SCROLL_AXIS_NONE;
+
+        onStartNestedScrollV1(horizontal, false);
+        onStartNestedScrollV1(neither, false);
+
+        onStartNestedScrollV2(horizontal, false);
+        onStartNestedScrollV2(neither, false);
+    }
+
+    @Test
+    public void onNestedScrollAccepted_callsParentsOnStartNestedScrollWithCorrectParams()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent).onStartNestedScroll(mNestedScrollView, mNestedScrollView,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScrollAccepted_callsParentsOnNestedScrollAcceptedWithCorrectParams()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+
+                mNestedScrollView.onNestedScrollAccepted(
+                        mChild,
+                        mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL,
+                        ViewCompat.TYPE_NON_TOUCH);
+
+                    verify(mParent, times(1)).onNestedScrollAccepted(
+                            mNestedScrollView,
+                            mNestedScrollView,
+                            ViewCompat.SCROLL_AXIS_VERTICAL,
+                            ViewCompat.TYPE_NON_TOUCH);
+                    verify(mParent, times(1)).onNestedScrollAccepted(
+                            any(View.class),
+                            any(View.class),
+                            anyInt(),
+                            anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScrollAccepted_withBothOrientations_pOnNestedScrollAcceptedCalledWithVert()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+
+                mNestedScrollView.onNestedScrollAccepted(
+                        mChild,
+                        mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL | ViewCompat.SCROLL_AXIS_HORIZONTAL,
+                        ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, times(1)).onNestedScrollAccepted(
+                        any(View.class),
+                        any(View.class),
+                        eq(ViewCompat.SCROLL_AXIS_VERTICAL),
+                        anyInt());
+                verify(mParent, times(1)).onNestedScrollAccepted(
+                        any(View.class),
+                        any(View.class),
+                        anyInt(),
+                        anyInt());
+
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScrollAccepted_parentRejects_parentOnNestedScrollAcceptedNotCalled()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(false)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+
+                mNestedScrollView.onNestedScrollAccepted(
+                        mChild,
+                        mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL,
+                        ViewCompat.TYPE_TOUCH);
+
+                verify(mParent, never()).onNestedScrollAccepted(
+                        any(View.class),
+                        any(View.class),
+                        anyInt(),
+                        anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScrollAccepted_v1_callsParentWithTypeTouch() throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+
+                mNestedScrollView.onNestedScrollAccepted(
+                        mChild,
+                        mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL);
+
+                verify(mParent, times(1)).onNestedScrollAccepted(
+                        any(View.class),
+                        any(View.class),
+                        anyInt(),
+                        eq(ViewCompat.TYPE_TOUCH));
+                verify(mParent, times(1)).onNestedScrollAccepted(
+                        any(View.class),
+                        any(View.class),
+                        anyInt(),
+                        anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onStopNestedScroll_parentOnStopNestedScrollCalledWithCorrectParams()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                            ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+
+                mNestedScrollView.onStopNestedScroll(mChild, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, times(1)).onStopNestedScroll(mNestedScrollView,
+                        ViewCompat.TYPE_NON_TOUCH);
+                verify(mParent, times(1)).onStopNestedScroll(any(View.class), anyInt());
+            }
+        });
+    }
+
+    // TODO(shepshapard), test with interactions where scroll type changes.
+
+    @Test
+    public void onStopNestedScroll_parentRejects_parentOnStopNestedScrollNotCalled()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(false)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL);
+
+                mNestedScrollView.onStopNestedScroll(mChild, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, never()).onStopNestedScroll(any(View.class), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onStopNestedScroll_calledWithTypeNotYetAccepted_parentOnStopNestedScrollNotCalled()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+
+                mNestedScrollView.onStopNestedScroll(mChild, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, never()).onStopNestedScroll(any(View.class), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onStopNestedScroll_v1_parentOnStopNestedScrollCalledWithTypeTouch()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL);
+
+                mNestedScrollView.onStopNestedScroll(mChild);
+
+                verify(mParent, times(1)).onStopNestedScroll(any(View.class),
+                        eq(ViewCompat.TYPE_TOUCH));
+                verify(mParent, times(1)).onStopNestedScroll(any(View.class), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_nsvScrolls() throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, 50, ViewCompat.TYPE_NON_TOUCH);
+
+                assertThat(mNestedScrollView.getScrollY(), is(50));
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_negativeScroll_nsvScrollsNegative() throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mNestedScrollView.scrollTo(0, 50);
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, -50, ViewCompat.TYPE_NON_TOUCH);
+
+                assertThat(mNestedScrollView.getScrollY(), is(0));
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_nsvConsumesEntireScroll_correctScrollDistancesPastToParent()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, 50, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, times(1)).onNestedScroll(any(View.class), eq(0), eq(50), eq(0),
+                        eq(0), anyInt());
+                verify(mParent, times(1)).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_nsvCanOnlyConsumePartOfScroll_correctScrollDistancesPastToParent()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, 75, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, times(1)).onNestedScroll(any(View.class), eq(0), eq(50), eq(0),
+                        eq(25), anyInt());
+                verify(mParent, times(1)).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_nsvCanOnlyConsumePartOfScrollNeg_correctScrollDistancesPastToParent()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+                mNestedScrollView.scrollTo(0, 50);
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, -75, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, times(1)).onNestedScroll(any(View.class), eq(0), eq(-50), eq(0),
+                        eq(-25), anyInt());
+                verify(mParent, times(1)).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_nsvIsAtEndOfScroll_correctScrollDistancesPastToParent()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+                mNestedScrollView.scrollTo(0, 50);
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, 50, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, times(1)).onNestedScroll(any(View.class), eq(0), eq(0), eq(0),
+                        eq(50), anyInt());
+                verify(mParent, times(1)).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_parentRejects_parentOnNestedScrollNotCalled() throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(false)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, 50, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, never()).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_calledWithTypeNotYetAccepted_parentOnStopNestedScrollNotCalled()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, 50, ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, never()).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedScroll_v1_parentOnNestedScrollCalledWithTypeTouch()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild(50, 100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL);
+
+                mNestedScrollView.onNestedScroll(mChild, 0, 0, 0, 50);
+
+                verify(mParent, times(1)).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), eq(ViewCompat.TYPE_TOUCH));
+                verify(mParent, times(1)).onNestedScroll(any(View.class), anyInt(), anyInt(),
+                        anyInt(), anyInt(), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedPreScroll_parentOnNestedPreScrollCalledWithCorrectParams()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+
+                mNestedScrollView.onNestedPreScroll(mChild, 1, 2, new int[]{0, 0},
+                        ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, times(1)).onNestedPreScroll(eq(mNestedScrollView), eq(1), eq(2),
+                        eq(new int[]{0, 0}), eq(ViewCompat.TYPE_NON_TOUCH));
+                verify(mParent, times(1)).onNestedPreScroll(any(View.class), anyInt(), anyInt(),
+                        any(int[].class), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedPreScroll_parentRejects_parentOnNestedPreScrollNotCalled()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(false)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
+
+                mNestedScrollView.onNestedPreScroll(mChild, 1, 2, new int[2],
+                        ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, never()).onNestedPreScroll(any(View.class), anyInt(), anyInt(),
+                        any(int[].class), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedPreScroll_calledWithTypeNotYetAccepted_parentOnStopNestedScrollNotCalled()
+            throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+
+                mNestedScrollView.onNestedPreScroll(mChild, 1, 2, new int[2],
+                        ViewCompat.TYPE_NON_TOUCH);
+
+                verify(mParent, never()).onNestedPreScroll(any(View.class), anyInt(), anyInt(),
+                        any(int[].class), anyInt());
+            }
+        });
+    }
+
+    @Test
+    public void onNestedPreScroll_v1_parentOnNestedPreScrollCalledWithTypeTouch() throws Throwable {
+        setupNestedScrollViewWithParentAndChild();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                doReturn(true)
+                        .when(mParent)
+                        .onStartNestedScroll(any(View.class), any(View.class), anyInt(), anyInt());
+                mNestedScrollView.onNestedScrollAccepted(mChild, mChild,
+                        ViewCompat.SCROLL_AXIS_VERTICAL);
+                int[] consumed = new int[2];
+
+                mNestedScrollView.onNestedPreScroll(mChild, 1, 2, consumed);
+
+                verify(mParent, times(1)).onNestedPreScroll(any(View.class), anyInt(), anyInt(),
+                        any(int[].class), eq(ViewCompat.TYPE_TOUCH));
+                verify(mParent, times(1)).onNestedPreScroll(any(View.class), anyInt(), anyInt(),
+                        any(int[].class), anyInt());
+            }
+        });
+    }
+
+    private void onStartNestedScrollV1(int iScrollAxis, boolean oRetValue) {
+        boolean retVal = mNestedScrollView.onStartNestedScroll(mChild, mChild, iScrollAxis);
+        assertThat(retVal, is(oRetValue));
+    }
+
+    private void onStartNestedScrollV2(int iScrollAxis, boolean oRetValue) {
+        boolean retVal = mNestedScrollView.onStartNestedScroll(mChild, mChild, iScrollAxis,
+                ViewCompat.TYPE_TOUCH);
+        assertThat(retVal, is(oRetValue));
+    }
+
+    private void setupNestedScrollViewWithParentAndChild() throws Throwable {
+        setupNestedScrollViewWithParentAndChild(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+    }
+
+    private void setupNestedScrollViewWithParentAndChild(int nestedScrollViewHeight,
+            int childHeight) throws Throwable {
+        final TestContentView testContentView =
+                mActivityTestRule.getActivity().findViewById(R.id.testContentView);
+
+        mNestedScrollView.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, nestedScrollViewHeight));
+        mNestedScrollView.setMinimumHeight(nestedScrollViewHeight);
+
+        mChild.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, childHeight));
+        mChild.setMinimumHeight(childHeight);
+
+        testContentView.expectLayouts(1);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mNestedScrollView.addView(mChild);
+                mParent.addView(mNestedScrollView);
+                testContentView.addView(mParent);
+            }
+        });
+        testContentView.awaitLayouts(2);
+    }
+
+    public class NestedScrollingSpyView extends FrameLayout implements NestedScrollingChild2,
+            NestedScrollingParent2 {
+
+        public NestedScrollingSpyView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
+                int type) {
+            return false;
+        }
+
+        @Override
+        public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes,
+                int type) {
+
+        }
+
+        @Override
+        public void onStopNestedScroll(@NonNull View target, int type) {
+
+        }
+
+        @Override
+        public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed, int type) {
+
+        }
+
+        @Override
+        public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
+                int type) {
+
+        }
+
+        @Override
+        public boolean startNestedScroll(int axes, int type) {
+            return false;
+        }
+
+        @Override
+        public void stopNestedScroll(int type) {
+
+        }
+
+        @Override
+        public boolean hasNestedScrollingParent(int type) {
+            return false;
+        }
+
+        @Override
+        public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
+                int dyUnconsumed, @Nullable int[] offsetInWindow, int type) {
+            return false;
+        }
+
+        @Override
+        public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
+                @Nullable int[] offsetInWindow, int type) {
+            return false;
+        }
+    }
+
+}
diff --git a/compat/src/androidTest/java/androidx/core/widget/TestContentView.java b/compat/src/androidTest/java/androidx/core/widget/TestContentView.java
new file mode 100644
index 0000000..510041a
--- /dev/null
+++ b/compat/src/androidTest/java/androidx/core/widget/TestContentView.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 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 androidx.core.widget;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestContentView extends FrameLayout {
+
+    private CountDownLatch mLayoutCountDownLatch;
+
+    public TestContentView(@NonNull Context context) {
+        super(context);
+    }
+
+    public TestContentView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public TestContentView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @SuppressWarnings("unused")
+    public TestContentView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public void expectLayouts(int count) {
+        mLayoutCountDownLatch = new CountDownLatch(count);
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    public void awaitLayouts(int seconds) throws InterruptedException {
+        assertThat(mLayoutCountDownLatch.await(seconds, TimeUnit.SECONDS), equalTo(true));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mLayoutCountDownLatch != null) {
+            mLayoutCountDownLatch.countDown();
+        }
+    }
+}
diff --git a/compat/src/androidTest/java/androidx/core/widget/TestContentViewActivity.java b/compat/src/androidTest/java/androidx/core/widget/TestContentViewActivity.java
new file mode 100644
index 0000000..5ecb46d
--- /dev/null
+++ b/compat/src/androidTest/java/androidx/core/widget/TestContentViewActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 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 androidx.core.widget;
+
+import android.support.v4.BaseTestActivity;
+
+import androidx.core.test.R;
+
+public class TestContentViewActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.test_content_view;
+    }
+}
diff --git a/compat/src/androidTest/res/layout/test_content_view.xml b/compat/src/androidTest/res/layout/test_content_view.xml
new file mode 100644
index 0000000..6b179ad
--- /dev/null
+++ b/compat/src/androidTest/res/layout/test_content_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 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.
+  -->
+
+<androidx.core.widget.TestContentView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/testContentView"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/compat/src/main/java/androidx/core/app/ActivityOptionsCompat.java b/compat/src/main/java/androidx/core/app/ActivityOptionsCompat.java
index 3f2c8ab..49f6c4a 100644
--- a/compat/src/main/java/androidx/core/app/ActivityOptionsCompat.java
+++ b/compat/src/main/java/androidx/core/app/ActivityOptionsCompat.java
@@ -66,7 +66,8 @@
     public static ActivityOptionsCompat makeCustomAnimation(@NonNull Context context,
             int enterResId, int exitResId) {
         if (Build.VERSION.SDK_INT >= 16) {
-            return createImpl(ActivityOptions.makeCustomAnimation(context, enterResId, exitResId));
+            return new ActivityOptionsCompatImpl(ActivityOptions.makeCustomAnimation(context,
+                    enterResId, exitResId));
         }
         return new ActivityOptionsCompat();
     }
@@ -95,7 +96,7 @@
     public static ActivityOptionsCompat makeScaleUpAnimation(@NonNull View source,
             int startX, int startY, int startWidth, int startHeight) {
         if (Build.VERSION.SDK_INT >= 16) {
-            return createImpl(ActivityOptions.makeScaleUpAnimation(
+            return new ActivityOptionsCompatImpl(ActivityOptions.makeScaleUpAnimation(
                     source, startX, startY, startWidth, startHeight));
         }
         return new ActivityOptionsCompat();
@@ -119,7 +120,7 @@
     public static ActivityOptionsCompat makeClipRevealAnimation(@NonNull View source,
             int startX, int startY, int width, int height) {
         if (Build.VERSION.SDK_INT >= 23) {
-            return createImpl(ActivityOptions.makeClipRevealAnimation(
+            return new ActivityOptionsCompatImpl(ActivityOptions.makeClipRevealAnimation(
                     source, startX, startY, width, height));
         }
         return new ActivityOptionsCompat();
@@ -148,7 +149,7 @@
     public static ActivityOptionsCompat makeThumbnailScaleUpAnimation(@NonNull View source,
             @NonNull Bitmap thumbnail, int startX, int startY) {
         if (Build.VERSION.SDK_INT >= 16) {
-            return createImpl(ActivityOptions.makeThumbnailScaleUpAnimation(
+            return new ActivityOptionsCompatImpl(ActivityOptions.makeThumbnailScaleUpAnimation(
                     source, thumbnail, startX, startY));
         }
         return new ActivityOptionsCompat();
@@ -176,7 +177,7 @@
     public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,
             @NonNull View sharedElement, @NonNull String sharedElementName) {
         if (Build.VERSION.SDK_INT >= 21) {
-            return createImpl(ActivityOptions.makeSceneTransitionAnimation(
+            return new ActivityOptionsCompatImpl(ActivityOptions.makeSceneTransitionAnimation(
                     activity, sharedElement, sharedElementName));
         }
         return new ActivityOptionsCompat();
@@ -212,7 +213,8 @@
                             sharedElements[i].first, sharedElements[i].second);
                 }
             }
-            return createImpl(ActivityOptions.makeSceneTransitionAnimation(activity, pairs));
+            return new ActivityOptionsCompatImpl(
+                    ActivityOptions.makeSceneTransitionAnimation(activity, pairs));
         }
         return new ActivityOptionsCompat();
     }
@@ -230,7 +232,7 @@
     @NonNull
     public static ActivityOptionsCompat makeTaskLaunchBehind() {
         if (Build.VERSION.SDK_INT >= 21) {
-            return createImpl(ActivityOptions.makeTaskLaunchBehind());
+            return new ActivityOptionsCompatImpl(ActivityOptions.makeTaskLaunchBehind());
         }
         return new ActivityOptionsCompat();
     }
@@ -242,27 +244,16 @@
     @NonNull
     public static ActivityOptionsCompat makeBasic() {
         if (Build.VERSION.SDK_INT >= 23) {
-            return createImpl(ActivityOptions.makeBasic());
+            return new ActivityOptionsCompatImpl(ActivityOptions.makeBasic());
         }
         return new ActivityOptionsCompat();
     }
 
     @RequiresApi(16)
-    private static ActivityOptionsCompat createImpl(ActivityOptions options) {
-        if (Build.VERSION.SDK_INT >= 24) {
-            return new ActivityOptionsCompatApi24Impl(options);
-        } else if (Build.VERSION.SDK_INT >= 23) {
-            return new ActivityOptionsCompatApi23Impl(options);
-        } else {
-            return new ActivityOptionsCompatApi16Impl(options);
-        }
-    }
+    private static class ActivityOptionsCompatImpl extends ActivityOptionsCompat {
+        private final ActivityOptions mActivityOptions;
 
-    @RequiresApi(16)
-    private static class ActivityOptionsCompatApi16Impl extends ActivityOptionsCompat {
-        protected final ActivityOptions mActivityOptions;
-
-        ActivityOptionsCompatApi16Impl(ActivityOptions activityOptions) {
+        ActivityOptionsCompatImpl(ActivityOptions activityOptions) {
             mActivityOptions = activityOptions;
         }
 
@@ -273,35 +264,21 @@
 
         @Override
         public void update(ActivityOptionsCompat otherOptions) {
-            if (otherOptions instanceof ActivityOptionsCompatApi16Impl) {
-                ActivityOptionsCompatApi16Impl otherImpl =
-                        (ActivityOptionsCompatApi16Impl) otherOptions;
+            if (otherOptions instanceof ActivityOptionsCompatImpl) {
+                ActivityOptionsCompatImpl otherImpl =
+                        (ActivityOptionsCompatImpl) otherOptions;
                 mActivityOptions.update(otherImpl.mActivityOptions);
             }
         }
-    }
-
-    @RequiresApi(23)
-    private static class ActivityOptionsCompatApi23Impl extends ActivityOptionsCompatApi16Impl {
-        ActivityOptionsCompatApi23Impl(ActivityOptions activityOptions) {
-            super(activityOptions);
-        }
 
         @Override
         public void requestUsageTimeReport(PendingIntent receiver) {
             mActivityOptions.requestUsageTimeReport(receiver);
         }
-    }
-
-    @RequiresApi(24)
-    private static class ActivityOptionsCompatApi24Impl extends ActivityOptionsCompatApi23Impl {
-        ActivityOptionsCompatApi24Impl(ActivityOptions activityOptions) {
-            super(activityOptions);
-        }
 
         @Override
         public ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
-            return new ActivityOptionsCompatApi24Impl(
+            return new ActivityOptionsCompatImpl(
                     mActivityOptions.setLaunchBounds(screenSpacePixelRect));
         }
 
diff --git a/compat/src/main/java/androidx/core/app/NotificationCompatBuilder.java b/compat/src/main/java/androidx/core/app/NotificationCompatBuilder.java
index 6be24ff..6ec420a 100644
--- a/compat/src/main/java/androidx/core/app/NotificationCompatBuilder.java
+++ b/compat/src/main/java/androidx/core/app/NotificationCompatBuilder.java
@@ -153,25 +153,28 @@
             }
             mHeadsUpContentView = b.mHeadsUpContentView;
 
-            // Invisible actions should be stored in the extender so we need to check if one exists
-            // already.
-            Bundle carExtenderBundle =
-                    b.getExtras().getBundle(NotificationCompat.CarExtender.EXTRA_CAR_EXTENDER);
-            if (carExtenderBundle == null) {
-                carExtenderBundle = new Bundle();
+            if (b.mInvisibleActions.size() > 0) {
+                // Invisible actions should be stored in the extender so we need to check if one
+                // exists already.
+                Bundle carExtenderBundle =
+                        b.getExtras().getBundle(NotificationCompat.CarExtender.EXTRA_CAR_EXTENDER);
+                if (carExtenderBundle == null) {
+                    carExtenderBundle = new Bundle();
+                }
+                Bundle listBundle = new Bundle();
+                for (int i = 0; i < b.mInvisibleActions.size(); i++) {
+                    listBundle.putBundle(
+                            Integer.toString(i),
+                            NotificationCompatJellybean.getBundleForAction(
+                                    b.mInvisibleActions.get(i)));
+                }
+                carExtenderBundle.putBundle(
+                        NotificationCompat.CarExtender.EXTRA_INVISIBLE_ACTIONS, listBundle);
+                b.getExtras().putBundle(
+                        NotificationCompat.CarExtender.EXTRA_CAR_EXTENDER, carExtenderBundle);
+                mExtras.putBundle(
+                        NotificationCompat.CarExtender.EXTRA_CAR_EXTENDER, carExtenderBundle);
             }
-            Bundle listBundle = new Bundle();
-            for (int i = 0; i < b.mInvisibleActions.size(); i++) {
-                listBundle.putBundle(
-                        Integer.toString(i),
-                        NotificationCompatJellybean.getBundleForAction(b.mInvisibleActions.get(i)));
-            }
-            carExtenderBundle.putBundle(
-                    NotificationCompat.CarExtender.EXTRA_INVISIBLE_ACTIONS, listBundle);
-            b.getExtras().putBundle(
-                    NotificationCompat.CarExtender.EXTRA_CAR_EXTENDER, carExtenderBundle);
-            mExtras.putBundle(
-                    NotificationCompat.CarExtender.EXTRA_CAR_EXTENDER, carExtenderBundle);
         }
         if (Build.VERSION.SDK_INT >= 24) {
             mBuilder.setExtras(b.mExtras)
diff --git a/compat/src/main/java/androidx/core/app/SupportActivity.java b/compat/src/main/java/androidx/core/app/SupportActivity.java
index 5ec33ec..1e02c0a 100644
--- a/compat/src/main/java/androidx/core/app/SupportActivity.java
+++ b/compat/src/main/java/androidx/core/app/SupportActivity.java
@@ -19,16 +19,16 @@
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.app.Activity;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.ReportFragment;
 import android.os.Bundle;
 
 import androidx.annotation.CallSuper;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.collection.SimpleArrayMap;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ReportFragment;
 
 /**
  * Base class for composing together compatibility functionality
diff --git a/compat/src/main/java/androidx/core/app/TaskStackBuilder.java b/compat/src/main/java/androidx/core/app/TaskStackBuilder.java
index c3deff6..a059eac 100644
--- a/compat/src/main/java/androidx/core/app/TaskStackBuilder.java
+++ b/compat/src/main/java/androidx/core/app/TaskStackBuilder.java
@@ -28,7 +28,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.core.content.ContextCompat;
 
 import java.util.ArrayList;
@@ -77,36 +76,6 @@
         Intent getSupportParentActivityIntent();
     }
 
-    static class TaskStackBuilderBaseImpl {
-        public PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
-                int flags, Bundle options) {
-            intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-            return PendingIntent.getActivities(context, requestCode, intents, flags);
-        }
-    }
-
-    @RequiresApi(16)
-    static class TaskStackBuilderApi16Impl extends TaskStackBuilderBaseImpl {
-        @Override
-        public PendingIntent getPendingIntent(Context context, Intent[] intents, int requestCode,
-                int flags, Bundle options) {
-            intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-            return PendingIntent.getActivities(context, requestCode, intents, flags, options);
-        }
-    }
-
-    private static final TaskStackBuilderBaseImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 16) {
-            IMPL = new TaskStackBuilderApi16Impl();
-        } else {
-            IMPL = new TaskStackBuilderBaseImpl();
-        }
-    }
-
     private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
     private final Context mSourceContext;
 
@@ -367,8 +336,13 @@
         Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
         intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-        // Appropriate flags will be added by the call below.
-        return IMPL.getPendingIntent(mSourceContext, intents, requestCode, flags, options);
+
+        if (Build.VERSION.SDK_INT >= 16) {
+            return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags,
+                    options);
+        } else {
+            return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags);
+        }
     }
 
     /**
diff --git a/compat/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java b/compat/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java
index 0276654..ff927a6 100644
--- a/compat/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java
+++ b/compat/src/main/java/androidx/core/hardware/display/DisplayManagerCompat.java
@@ -24,14 +24,13 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 import java.util.WeakHashMap;
 
 /**
  * Helper for accessing features in {@link android.hardware.display.DisplayManager}.
  */
-public abstract class DisplayManagerCompat {
+public final class DisplayManagerCompat {
     private static final WeakHashMap<Context, DisplayManagerCompat> sInstances =
             new WeakHashMap<Context, DisplayManagerCompat>();
 
@@ -49,7 +48,10 @@
     public static final String DISPLAY_CATEGORY_PRESENTATION =
             "android.hardware.display.category.PRESENTATION";
 
-    DisplayManagerCompat() {
+    private final Context mContext;
+
+    private DisplayManagerCompat(Context context) {
+        mContext = context;
     }
 
     /**
@@ -60,11 +62,7 @@
         synchronized (sInstances) {
             DisplayManagerCompat instance = sInstances.get(context);
             if (instance == null) {
-                if (Build.VERSION.SDK_INT >= 17) {
-                    instance = new DisplayManagerCompatApi17Impl(context);
-                } else {
-                    instance = new DisplayManagerCompatApi14Impl(context);
-                }
+                instance = new DisplayManagerCompat(context);
                 sInstances.put(context, instance);
             }
             return instance;
@@ -81,7 +79,19 @@
      * @return The display object, or null if there is no valid display with the given id.
      */
     @Nullable
-    public abstract Display getDisplay(int displayId);
+    public Display getDisplay(int displayId) {
+        if (Build.VERSION.SDK_INT >= 17) {
+            return ((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE))
+                    .getDisplay(displayId);
+        }
+
+        Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
+                .getDefaultDisplay();
+        if (display.getDisplayId() == displayId) {
+            return display;
+        }
+        return null;
+    }
 
     /**
      * Gets all currently valid logical displays.
@@ -89,7 +99,16 @@
      * @return An array containing all displays.
      */
     @NonNull
-    public abstract Display[] getDisplays();
+    public Display[] getDisplays() {
+        if (Build.VERSION.SDK_INT >= 17) {
+            return ((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE))
+                    .getDisplays();
+        }
+
+        Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
+                .getDefaultDisplay();
+        return new Display[] { display };
+    }
 
     /**
      * Gets all currently valid logical displays of the specified category.
@@ -108,56 +127,17 @@
      * @see #DISPLAY_CATEGORY_PRESENTATION
      */
     @NonNull
-    public abstract Display[] getDisplays(String category);
-
-    private static class DisplayManagerCompatApi14Impl extends DisplayManagerCompat {
-        private final WindowManager mWindowManager;
-
-        DisplayManagerCompatApi14Impl(Context context) {
-            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+    public Display[] getDisplays(@Nullable String category) {
+        if (Build.VERSION.SDK_INT >= 17) {
+            return ((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE))
+                    .getDisplays(category);
+        }
+        if (category == null) {
+            return new Display[0];
         }
 
-        @Override
-        public Display getDisplay(int displayId) {
-            Display display = mWindowManager.getDefaultDisplay();
-            if (display.getDisplayId() == displayId) {
-                return display;
-            }
-            return null;
-        }
-
-        @Override
-        public Display[] getDisplays() {
-            return new Display[] { mWindowManager.getDefaultDisplay() };
-        }
-
-        @Override
-        public Display[] getDisplays(String category) {
-            return category == null ? getDisplays() : new Display[0];
-        }
-    }
-
-    @RequiresApi(17)
-    private static class DisplayManagerCompatApi17Impl extends DisplayManagerCompat {
-        private final DisplayManager mDisplayManager;
-
-        DisplayManagerCompatApi17Impl(Context context) {
-            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
-        }
-
-        @Override
-        public Display getDisplay(int displayId) {
-            return mDisplayManager.getDisplay(displayId);
-        }
-
-        @Override
-        public Display[] getDisplays() {
-            return mDisplayManager.getDisplays();
-        }
-
-        @Override
-        public Display[] getDisplays(String category) {
-            return mDisplayManager.getDisplays(category);
-        }
+        Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
+                .getDefaultDisplay();
+        return new Display[] { display };
     }
 }
diff --git a/compat/src/main/java/androidx/core/view/AccessibilityDelegateCompat.java b/compat/src/main/java/androidx/core/view/AccessibilityDelegateCompat.java
index 00d5ac6..0f36f42 100644
--- a/compat/src/main/java/androidx/core/view/AccessibilityDelegateCompat.java
+++ b/compat/src/main/java/androidx/core/view/AccessibilityDelegateCompat.java
@@ -25,7 +25,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 
-import androidx.annotation.RequiresApi;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.core.view.accessibility.AccessibilityNodeProviderCompat;
 
@@ -46,160 +45,75 @@
  */
 public class AccessibilityDelegateCompat {
 
-    static class AccessibilityDelegateBaseImpl {
-        public AccessibilityDelegate newAccessibilityDelegateBridge(
-                final AccessibilityDelegateCompat compat) {
-            return new AccessibilityDelegate() {
-                @Override
-                public boolean dispatchPopulateAccessibilityEvent(View host,
-                        AccessibilityEvent event) {
-                    return compat.dispatchPopulateAccessibilityEvent(host, event);
-                }
+    private static final class AccessibilityDelegateAdapter extends AccessibilityDelegate {
+        private final AccessibilityDelegateCompat mCompat;
 
-                @Override
-                public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-                    compat.onInitializeAccessibilityEvent(host, event);
-                }
-
-                @Override
-                public void onInitializeAccessibilityNodeInfo(
-                        View host, AccessibilityNodeInfo info) {
-                    compat.onInitializeAccessibilityNodeInfo(host,
-                            AccessibilityNodeInfoCompat.wrap(info));
-                }
-
-                @Override
-                public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-                    compat.onPopulateAccessibilityEvent(host, event);
-                }
-
-                @Override
-                public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
-                        AccessibilityEvent event) {
-                    return compat.onRequestSendAccessibilityEvent(host, child, event);
-                }
-
-                @Override
-                public void sendAccessibilityEvent(View host, int eventType) {
-                    compat.sendAccessibilityEvent(host, eventType);
-                }
-
-                @Override
-                public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
-                    compat.sendAccessibilityEventUnchecked(host, event);
-                }
-            };
-        }
-
-        public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(
-                AccessibilityDelegate delegate, View host) {
-            // Do nothing. Added in API 16.
-            return null;
-        }
-
-        public boolean performAccessibilityAction(AccessibilityDelegate delegate, View host,
-                int action, Bundle args) {
-            // Do nothing. Added in API 16.
-            return false;
-        }
-    }
-
-    @RequiresApi(16)
-    static class AccessibilityDelegateApi16Impl extends AccessibilityDelegateBaseImpl {
-        @Override
-        public AccessibilityDelegate newAccessibilityDelegateBridge(
-                final AccessibilityDelegateCompat compat) {
-            return new AccessibilityDelegate()  {
-                @Override
-                public boolean dispatchPopulateAccessibilityEvent(View host,
-                        AccessibilityEvent event) {
-                    return compat.dispatchPopulateAccessibilityEvent(host, event);
-                }
-
-                @Override
-                public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-                    compat.onInitializeAccessibilityEvent(host, event);
-                }
-
-                @Override
-                public void onInitializeAccessibilityNodeInfo(
-                        View host, AccessibilityNodeInfo info) {
-                    compat.onInitializeAccessibilityNodeInfo(host,
-                            AccessibilityNodeInfoCompat.wrap(info));
-                }
-
-                @Override
-                public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-                    compat.onPopulateAccessibilityEvent(host, event);
-                }
-
-                @Override
-                public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
-                        AccessibilityEvent event) {
-                    return compat.onRequestSendAccessibilityEvent(host, child, event);
-                }
-
-                @Override
-                public void sendAccessibilityEvent(View host, int eventType) {
-                    compat.sendAccessibilityEvent(host, eventType);
-                }
-
-                @Override
-                public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
-                    compat.sendAccessibilityEventUnchecked(host, event);
-                }
-
-                @Override
-                public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
-                    AccessibilityNodeProviderCompat provider =
-                        compat.getAccessibilityNodeProvider(host);
-                    return (provider != null)
-                            ? (AccessibilityNodeProvider) provider.getProvider() : null;
-                }
-
-                @Override
-                public boolean performAccessibilityAction(View host, int action, Bundle args) {
-                    return compat.performAccessibilityAction(host, action, args);
-                }
-            };
+        AccessibilityDelegateAdapter(AccessibilityDelegateCompat compat) {
+            mCompat = compat;
         }
 
         @Override
-        public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(
-                AccessibilityDelegate delegate, View host) {
-            Object provider = delegate.getAccessibilityNodeProvider(host);
-            if (provider != null) {
-                return new AccessibilityNodeProviderCompat(provider);
-            }
-            return null;
+        public boolean dispatchPopulateAccessibilityEvent(View host,
+                AccessibilityEvent event) {
+            return mCompat.dispatchPopulateAccessibilityEvent(host, event);
         }
 
         @Override
-        public boolean performAccessibilityAction(AccessibilityDelegate delegate, View host,
-                int action, Bundle args) {
-            return delegate.performAccessibilityAction(host, action, args);
+        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+            mCompat.onInitializeAccessibilityEvent(host, event);
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(
+                View host, AccessibilityNodeInfo info) {
+            mCompat.onInitializeAccessibilityNodeInfo(host,
+                    AccessibilityNodeInfoCompat.wrap(info));
+        }
+
+        @Override
+        public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+            mCompat.onPopulateAccessibilityEvent(host, event);
+        }
+
+        @Override
+        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+                AccessibilityEvent event) {
+            return mCompat.onRequestSendAccessibilityEvent(host, child, event);
+        }
+
+        @Override
+        public void sendAccessibilityEvent(View host, int eventType) {
+            mCompat.sendAccessibilityEvent(host, eventType);
+        }
+
+        @Override
+        public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
+            mCompat.sendAccessibilityEventUnchecked(host, event);
+        }
+
+        @Override
+        public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+            AccessibilityNodeProviderCompat provider =
+                    mCompat.getAccessibilityNodeProvider(host);
+            return (provider != null)
+                    ? (AccessibilityNodeProvider) provider.getProvider() : null;
+        }
+
+        @Override
+        public boolean performAccessibilityAction(View host, int action, Bundle args) {
+            return mCompat.performAccessibilityAction(host, action, args);
         }
     }
 
-    private static final AccessibilityDelegateBaseImpl IMPL;
-    private static final AccessibilityDelegate DEFAULT_DELEGATE;
+    private static final AccessibilityDelegate DEFAULT_DELEGATE = new AccessibilityDelegate();
 
-    static {
-        if (Build.VERSION.SDK_INT >= 16) { // JellyBean
-            IMPL = new AccessibilityDelegateApi16Impl();
-        } else {
-            IMPL = new AccessibilityDelegateBaseImpl();
-        }
-        DEFAULT_DELEGATE = new AccessibilityDelegate();
-    }
-
-    final AccessibilityDelegate mBridge;
+    private final AccessibilityDelegate mBridge;
 
     /**
      * Creates a new instance.
      */
     public AccessibilityDelegateCompat() {
-        mBridge = IMPL.newAccessibilityDelegateBridge(this);
+        mBridge = new AccessibilityDelegateAdapter(this);
     }
 
     /**
@@ -369,7 +283,13 @@
      * @see AccessibilityNodeProviderCompat
      */
     public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
-        return IMPL.getAccessibilityNodeProvider(DEFAULT_DELEGATE, host);
+        if (Build.VERSION.SDK_INT >= 16) {
+            Object provider = DEFAULT_DELEGATE.getAccessibilityNodeProvider(host);
+            if (provider != null) {
+                return new AccessibilityNodeProviderCompat(provider);
+            }
+        }
+        return null;
     }
 
     /**
@@ -389,6 +309,9 @@
      *      View#performAccessibilityAction(int, Bundle)
      */
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        return IMPL.performAccessibilityAction(DEFAULT_DELEGATE, host, action, args);
+        if (Build.VERSION.SDK_INT >= 16) {
+            return DEFAULT_DELEGATE.performAccessibilityAction(host, action, args);
+        }
+        return false;
     }
 }
diff --git a/compat/src/main/java/androidx/core/view/LayoutInflaterCompat.java b/compat/src/main/java/androidx/core/view/LayoutInflaterCompat.java
index 889f9a3..437d56a 100644
--- a/compat/src/main/java/androidx/core/view/LayoutInflaterCompat.java
+++ b/compat/src/main/java/androidx/core/view/LayoutInflaterCompat.java
@@ -24,7 +24,6 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
 import java.lang.reflect.Field;
 
@@ -68,7 +67,7 @@
      * that already had a Factory2 registered. We work around that bug here. If we can't we
      * log an error.
      */
-    static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
+    private static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
         if (!sCheckedField) {
             try {
                 sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
@@ -90,61 +89,6 @@
         }
     }
 
-    static class LayoutInflaterCompatBaseImpl {
-        @SuppressWarnings("deprecation")
-        public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
-            final LayoutInflater.Factory2 factory2 = factory != null
-                    ? new Factory2Wrapper(factory) : null;
-            setFactory2(inflater, factory2);
-        }
-
-        public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
-            inflater.setFactory2(factory);
-
-            final LayoutInflater.Factory f = inflater.getFactory();
-            if (f instanceof LayoutInflater.Factory2) {
-                // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
-                // We will now try and force set the merged factory to mFactory2
-                forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
-            } else {
-                // Else, we will force set the original wrapped Factory2
-                forceSetFactory2(inflater, factory);
-            }
-        }
-
-        @SuppressWarnings("deprecation")
-        public LayoutInflaterFactory getFactory(LayoutInflater inflater) {
-            LayoutInflater.Factory factory = inflater.getFactory();
-            if (factory instanceof Factory2Wrapper) {
-                return ((Factory2Wrapper) factory).mDelegateFactory;
-            }
-            return null;
-        }
-    }
-
-    @RequiresApi(21)
-    static class LayoutInflaterCompatApi21Impl extends LayoutInflaterCompatBaseImpl {
-        @SuppressWarnings("deprecation")
-        @Override
-        public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
-            inflater.setFactory2(factory != null ? new Factory2Wrapper(factory) : null);
-        }
-
-        @Override
-        public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
-            inflater.setFactory2(factory);
-        }
-    }
-
-    static final LayoutInflaterCompatBaseImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new LayoutInflaterCompatApi21Impl();
-        } else {
-            IMPL = new LayoutInflaterCompatBaseImpl();
-        }
-    }
-
     /*
      * Hide the constructor.
      */
@@ -164,7 +108,23 @@
     @Deprecated
     public static void setFactory(
             @NonNull LayoutInflater inflater, @NonNull LayoutInflaterFactory factory) {
-        IMPL.setFactory(inflater, factory);
+        if (Build.VERSION.SDK_INT >= 21) {
+            inflater.setFactory2(factory != null ? new Factory2Wrapper(factory) : null);
+        } else {
+            final LayoutInflater.Factory2 factory2 = factory != null
+                    ? new Factory2Wrapper(factory) : null;
+            inflater.setFactory2(factory2);
+
+            final LayoutInflater.Factory f = inflater.getFactory();
+            if (f instanceof LayoutInflater.Factory2) {
+                // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
+                // We will now try and force set the merged factory to mFactory2
+                forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
+            } else {
+                // Else, we will force set the original wrapped Factory2
+                forceSetFactory2(inflater, factory2);
+            }
+        }
     }
 
     /**
@@ -176,7 +136,19 @@
      */
     public static void setFactory2(
             @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
-        IMPL.setFactory2(inflater, factory);
+        inflater.setFactory2(factory);
+
+        if (Build.VERSION.SDK_INT < 21) {
+            final LayoutInflater.Factory f = inflater.getFactory();
+            if (f instanceof LayoutInflater.Factory2) {
+                // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
+                // We will now try and force set the merged factory to mFactory2
+                forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
+            } else {
+                // Else, we will force set the original wrapped Factory2
+                forceSetFactory2(inflater, factory);
+            }
+        }
     }
 
     /**
@@ -194,6 +166,10 @@
      */
     @Deprecated
     public static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
-        return IMPL.getFactory(inflater);
+        LayoutInflater.Factory factory = inflater.getFactory();
+        if (factory instanceof Factory2Wrapper) {
+            return ((Factory2Wrapper) factory).mDelegateFactory;
+        }
+        return null;
     }
 }
diff --git a/compat/src/main/java/androidx/core/view/MenuItemCompat.java b/compat/src/main/java/androidx/core/view/MenuItemCompat.java
index d367052..c6b77e5 100644
--- a/compat/src/main/java/androidx/core/view/MenuItemCompat.java
+++ b/compat/src/main/java/androidx/core/view/MenuItemCompat.java
@@ -26,7 +26,6 @@
 import android.view.MenuItem;
 import android.view.View;
 
-import androidx.annotation.RequiresApi;
 import androidx.core.internal.view.SupportMenuItem;
 
 /**
@@ -88,26 +87,6 @@
     public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
 
     /**
-     * Interface for the full API.
-     */
-    interface MenuVersionImpl {
-        void setContentDescription(MenuItem item, CharSequence contentDescription);
-        CharSequence getContentDescription(MenuItem item);
-        void setTooltipText(MenuItem item, CharSequence tooltipText);
-        CharSequence getTooltipText(MenuItem item);
-        void setShortcut(MenuItem item, char numericChar, char alphaChar, int numericModifiers,
-                int alphaModifiers);
-        void setAlphabeticShortcut(MenuItem item, char alphaChar, int alphaModifiers);
-        int getAlphabeticModifiers(MenuItem item);
-        void setNumericShortcut(MenuItem item, char numericChar, int numericModifiers);
-        int getNumericModifiers(MenuItem item);
-        void setIconTintList(MenuItem item, ColorStateList tint);
-        ColorStateList getIconTintList(MenuItem item);
-        void setIconTintMode(MenuItem item, PorterDuff.Mode tintMode);
-        PorterDuff.Mode getIconTintMode(MenuItem item);
-    }
-
-    /**
      * Interface definition for a callback to be invoked when a menu item marked with {@link
      * #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is expanded or collapsed.
      *
@@ -139,148 +118,6 @@
         boolean onMenuItemActionCollapse(MenuItem item);
     }
 
-    static class MenuItemCompatBaseImpl implements MenuVersionImpl {
-        @Override
-        public void setContentDescription(MenuItem item, CharSequence contentDescription) {
-        }
-
-        @Override
-        public CharSequence getContentDescription(MenuItem item) {
-            return null;
-        }
-
-        @Override
-        public void setTooltipText(MenuItem item, CharSequence tooltipText) {
-        }
-
-        @Override
-        public CharSequence getTooltipText(MenuItem item) {
-            return null;
-        }
-
-        @Override
-        public void setShortcut(MenuItem item, char numericChar, char alphaChar,
-                int numericModifiers, int alphaModifiers) {
-        }
-
-        @Override
-        public void setAlphabeticShortcut(MenuItem item, char alphaChar, int alphaModifiers) {
-        }
-
-        @Override
-        public int getAlphabeticModifiers(MenuItem item) {
-            return 0;
-        }
-
-        @Override
-        public void setNumericShortcut(MenuItem item, char numericChar, int numericModifiers) {
-        }
-
-        @Override
-        public int getNumericModifiers(MenuItem item) {
-            return 0;
-        }
-
-        @Override
-        public void setIconTintList(MenuItem item, ColorStateList tint) {
-        }
-
-        @Override
-        public ColorStateList getIconTintList(MenuItem item) {
-            return null;
-        }
-
-        @Override
-        public void setIconTintMode(MenuItem item, PorterDuff.Mode tintMode) {
-        }
-
-        @Override
-        public PorterDuff.Mode getIconTintMode(MenuItem item) {
-            return null;
-        }
-    }
-
-    @RequiresApi(26)
-    static class MenuItemCompatApi26Impl extends MenuItemCompatBaseImpl {
-        @Override
-        public void setContentDescription(MenuItem item, CharSequence contentDescription) {
-            item.setContentDescription(contentDescription);
-        }
-
-        @Override
-        public CharSequence getContentDescription(MenuItem item) {
-            return item.getContentDescription();
-        }
-
-        @Override
-        public void setTooltipText(MenuItem item, CharSequence tooltipText) {
-            item.setTooltipText(tooltipText);
-        }
-
-        @Override
-        public CharSequence getTooltipText(MenuItem item) {
-            return item.getTooltipText();
-        }
-
-        @Override
-        public void setShortcut(MenuItem item, char numericChar, char alphaChar,
-                int numericModifiers, int alphaModifiers) {
-            item.setShortcut(numericChar, alphaChar, numericModifiers, alphaModifiers);
-        }
-
-        @Override
-        public void setAlphabeticShortcut(MenuItem item, char alphaChar, int alphaModifiers) {
-            item.setAlphabeticShortcut(alphaChar, alphaModifiers);
-        }
-
-        @Override
-        public int getAlphabeticModifiers(MenuItem item) {
-            return item.getAlphabeticModifiers();
-        }
-
-        @Override
-        public void setNumericShortcut(MenuItem item, char numericChar, int numericModifiers) {
-            item.setNumericShortcut(numericChar, numericModifiers);
-        }
-
-        @Override
-        public int getNumericModifiers(MenuItem item) {
-            return item.getNumericModifiers();
-        }
-
-        @Override
-        public void setIconTintList(MenuItem item, ColorStateList tint) {
-            item.setIconTintList(tint);
-        }
-
-        @Override
-        public ColorStateList getIconTintList(MenuItem item) {
-            return item.getIconTintList();
-        }
-
-        @Override
-        public void setIconTintMode(MenuItem item, PorterDuff.Mode tintMode) {
-            item.setIconTintMode(tintMode);
-        }
-
-        @Override
-        public PorterDuff.Mode getIconTintMode(MenuItem item) {
-            return item.getIconTintMode();
-        }
-    }
-
-    /**
-     * Select the correct implementation to use for the current platform.
-     */
-    static final MenuVersionImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 26) {
-            IMPL = new MenuItemCompatApi26Impl();
-        } else {
-            IMPL = new MenuItemCompatBaseImpl();
-        }
-    }
-
     // -------------------------------------------------------------------
 
     /**
@@ -484,8 +321,8 @@
     public static void setContentDescription(MenuItem item, CharSequence contentDescription) {
         if (item instanceof SupportMenuItem) {
             ((SupportMenuItem) item).setContentDescription(contentDescription);
-        } else {
-            IMPL.setContentDescription(item, contentDescription);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            item.setContentDescription(contentDescription);
         }
     }
 
@@ -498,7 +335,10 @@
         if (item instanceof SupportMenuItem) {
             return ((SupportMenuItem) item).getContentDescription();
         }
-        return IMPL.getContentDescription(item);
+        if (Build.VERSION.SDK_INT >= 26) {
+            return item.getContentDescription();
+        }
+        return null;
     }
 
     /**
@@ -510,8 +350,8 @@
     public static void setTooltipText(MenuItem item, CharSequence tooltipText) {
         if (item instanceof SupportMenuItem) {
             ((SupportMenuItem) item).setTooltipText(tooltipText);
-        } else {
-            IMPL.setTooltipText(item, tooltipText);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            item.setTooltipText(tooltipText);
         }
     }
 
@@ -524,7 +364,10 @@
         if (item instanceof SupportMenuItem) {
             return ((SupportMenuItem) item).getTooltipText();
         }
-        return IMPL.getTooltipText(item);
+        if (Build.VERSION.SDK_INT >= 26) {
+            return item.getTooltipText();
+        }
+        return null;
     }
 
     /**
@@ -554,8 +397,8 @@
         if (item instanceof SupportMenuItem) {
             ((SupportMenuItem) item).setShortcut(numericChar, alphaChar, numericModifiers,
                     alphaModifiers);
-        } else {
-            IMPL.setShortcut(item, numericChar, alphaChar, numericModifiers, alphaModifiers);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            item.setShortcut(numericChar, alphaChar, numericModifiers, alphaModifiers);
         }
     }
 
@@ -574,8 +417,8 @@
     public static void setNumericShortcut(MenuItem item, char numericChar, int numericModifiers) {
         if (item instanceof SupportMenuItem) {
             ((SupportMenuItem) item).setNumericShortcut(numericChar, numericModifiers);
-        } else {
-            IMPL.setNumericShortcut(item, numericChar, numericModifiers);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            item.setNumericShortcut(numericChar, numericModifiers);
         }
     }
 
@@ -593,7 +436,10 @@
         if (item instanceof SupportMenuItem) {
             return ((SupportMenuItem) item).getNumericModifiers();
         }
-        return IMPL.getNumericModifiers(item);
+        if (Build.VERSION.SDK_INT >= 26) {
+            return item.getNumericModifiers();
+        }
+        return 0;
     }
 
     /**
@@ -616,8 +462,8 @@
     public static void setAlphabeticShortcut(MenuItem item, char alphaChar, int alphaModifiers) {
         if (item instanceof SupportMenuItem) {
             ((SupportMenuItem) item).setAlphabeticShortcut(alphaChar, alphaModifiers);
-        } else {
-            IMPL.setAlphabeticShortcut(item, alphaChar, alphaModifiers);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            item.setAlphabeticShortcut(alphaChar, alphaModifiers);
         }
     }
 
@@ -635,7 +481,10 @@
         if (item instanceof SupportMenuItem) {
             return ((SupportMenuItem) item).getAlphabeticModifiers();
         }
-        return IMPL.getAlphabeticModifiers(item);
+        if (Build.VERSION.SDK_INT >= 26) {
+            return item.getAlphabeticModifiers();
+        }
+        return 0;
     }
 
     /**
@@ -653,8 +502,8 @@
     public static void setIconTintList(MenuItem item, ColorStateList tint) {
         if (item instanceof SupportMenuItem) {
             ((SupportMenuItem) item).setIconTintList(tint);
-        } else {
-            IMPL.setIconTintList(item, tint);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            item.setIconTintList(tint);
         }
     }
 
@@ -666,7 +515,10 @@
         if (item instanceof SupportMenuItem) {
             return ((SupportMenuItem) item).getIconTintList();
         }
-        return IMPL.getIconTintList(item);
+        if (Build.VERSION.SDK_INT >= 26) {
+            return item.getIconTintList();
+        }
+        return null;
     }
 
     /**
@@ -681,8 +533,8 @@
     public static void setIconTintMode(MenuItem item, PorterDuff.Mode tintMode) {
         if (item instanceof SupportMenuItem) {
             ((SupportMenuItem) item).setIconTintMode(tintMode);
-        } else {
-            IMPL.setIconTintMode(item, tintMode);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            item.setIconTintMode(tintMode);
         }
     }
 
@@ -696,7 +548,10 @@
         if (item instanceof SupportMenuItem) {
             return ((SupportMenuItem) item).getIconTintMode();
         }
-        return IMPL.getIconTintMode(item);
+        if (Build.VERSION.SDK_INT >= 26) {
+            return item.getIconTintMode();
+        }
+        return null;
     }
 
     private MenuItemCompat() {}
diff --git a/compat/src/main/java/androidx/core/view/ViewCompat.java b/compat/src/main/java/androidx/core/view/ViewCompat.java
index 2ed252a..60e5f56 100644
--- a/compat/src/main/java/androidx/core/view/ViewCompat.java
+++ b/compat/src/main/java/androidx/core/view/ViewCompat.java
@@ -51,7 +51,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.core.view.accessibility.AccessibilityNodeProviderCompat;
@@ -452,220 +451,26 @@
     private static Method sDispatchFinishTemporaryDetach;
     private static boolean sTempDetachBound;
 
-    static class ViewCompatBaseImpl {
-        private static WeakHashMap<View, String> sTransitionNameMap;
-        WeakHashMap<View, ViewPropertyAnimatorCompat> mViewPropertyAnimatorCompatMap = null;
-        private static Method sChildrenDrawingOrderMethod;
-        static Field sAccessibilityDelegateField;
-        static boolean sAccessibilityDelegateCheckFailed = false;
+    private static WeakHashMap<View, String> sTransitionNameMap;
+    private static WeakHashMap<View, ViewPropertyAnimatorCompat> sViewPropertyAnimatorMap = null;
 
-        public void setAccessibilityDelegate(View v,
-                @Nullable AccessibilityDelegateCompat delegate) {
-            v.setAccessibilityDelegate(delegate == null ? null : delegate.getBridge());
+    private static Method sChildrenDrawingOrderMethod;
+    private static Field sAccessibilityDelegateField;
+    private static boolean sAccessibilityDelegateCheckFailed = false;
+
+    private static ThreadLocal<Rect> sThreadLocalRect;
+
+    private static Rect getEmptyTempRect() {
+        if (sThreadLocalRect == null) {
+            sThreadLocalRect = new ThreadLocal<>();
         }
-
-        public boolean hasAccessibilityDelegate(View v) {
-            if (sAccessibilityDelegateCheckFailed) {
-                return false; // View implementation might have changed.
-            }
-            if (sAccessibilityDelegateField == null) {
-                try {
-                    sAccessibilityDelegateField = View.class
-                            .getDeclaredField("mAccessibilityDelegate");
-                    sAccessibilityDelegateField.setAccessible(true);
-                } catch (Throwable t) {
-                    sAccessibilityDelegateCheckFailed = true;
-                    return false;
-                }
-            }
-            try {
-                return sAccessibilityDelegateField.get(v) != null;
-            } catch (Throwable t) {
-                sAccessibilityDelegateCheckFailed = true;
-                return false;
-            }
+        Rect rect = sThreadLocalRect.get();
+        if (rect == null) {
+            rect = new Rect();
+            sThreadLocalRect.set(rect);
         }
-
-        public ViewPropertyAnimatorCompat animate(View view) {
-            if (mViewPropertyAnimatorCompatMap == null) {
-                mViewPropertyAnimatorCompatMap = new WeakHashMap<>();
-            }
-            ViewPropertyAnimatorCompat vpa = mViewPropertyAnimatorCompatMap.get(view);
-            if (vpa == null) {
-                vpa = new ViewPropertyAnimatorCompat(view);
-                mViewPropertyAnimatorCompatMap.put(view, vpa);
-            }
-            return vpa;
-        }
-
-        public void setTransitionName(View view, String transitionName) {
-            if (sTransitionNameMap == null) {
-                sTransitionNameMap = new WeakHashMap<>();
-            }
-            sTransitionNameMap.put(view, transitionName);
-        }
-
-        public String getTransitionName(View view) {
-            if (sTransitionNameMap == null) {
-                return null;
-            }
-            return sTransitionNameMap.get(view);
-        }
-
-        public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
-            if (sChildrenDrawingOrderMethod == null) {
-                try {
-                    sChildrenDrawingOrderMethod = ViewGroup.class
-                            .getDeclaredMethod("setChildrenDrawingOrderEnabled", boolean.class);
-                } catch (NoSuchMethodException e) {
-                    Log.e(TAG, "Unable to find childrenDrawingOrderEnabled", e);
-                }
-                sChildrenDrawingOrderMethod.setAccessible(true);
-            }
-            try {
-                sChildrenDrawingOrderMethod.invoke(viewGroup, enabled);
-            } catch (IllegalAccessException e) {
-                Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
-            } catch (InvocationTargetException e) {
-                Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
-            }
-        }
-
-        public void offsetLeftAndRight(View view, int offset) {
-            view.offsetLeftAndRight(offset);
-            if (view.getVisibility() == View.VISIBLE) {
-                tickleInvalidationFlag(view);
-
-                ViewParent parent = view.getParent();
-                if (parent instanceof View) {
-                    tickleInvalidationFlag((View) parent);
-                }
-            }
-        }
-
-        public void offsetTopAndBottom(View view, int offset) {
-            view.offsetTopAndBottom(offset);
-            if (view.getVisibility() == View.VISIBLE) {
-                tickleInvalidationFlag(view);
-
-                ViewParent parent = view.getParent();
-                if (parent instanceof View) {
-                    tickleInvalidationFlag((View) parent);
-                }
-            }
-        }
-
-        private static void tickleInvalidationFlag(View view) {
-            final float y = view.getTranslationY();
-            view.setTranslationY(y + 1);
-            view.setTranslationY(y);
-        }
-    }
-
-    @RequiresApi(21)
-    static class ViewCompatApi21Impl extends ViewCompatBaseImpl {
-        private static ThreadLocal<Rect> sThreadLocalRect;
-
-        @Override
-        public void setTransitionName(View view, String transitionName) {
-            view.setTransitionName(transitionName);
-        }
-
-        @Override
-        public String getTransitionName(View view) {
-            return view.getTransitionName();
-        }
-
-        @Override
-        public void offsetLeftAndRight(View view, int offset) {
-            final Rect parentRect = getEmptyTempRect();
-            boolean needInvalidateWorkaround = false;
-
-            final ViewParent parent = view.getParent();
-            if (parent instanceof View) {
-                final View p = (View) parent;
-                parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
-                // If the view currently does not currently intersect the parent (and is therefore
-                // not displayed) we may need need to invalidate
-                needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
-                        view.getRight(), view.getBottom());
-            }
-
-            // Now offset, invoking the API 11+ implementation (which contains its own workarounds)
-            super.offsetLeftAndRight(view, offset);
-
-            // The view has now been offset, so let's intersect the Rect and invalidate where
-            // the View is now displayed
-            if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
-                    view.getRight(), view.getBottom())) {
-                ((View) parent).invalidate(parentRect);
-            }
-        }
-
-        @Override
-        public void offsetTopAndBottom(View view, int offset) {
-            final Rect parentRect = getEmptyTempRect();
-            boolean needInvalidateWorkaround = false;
-
-            final ViewParent parent = view.getParent();
-            if (parent instanceof View) {
-                final View p = (View) parent;
-                parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
-                // If the view currently does not currently intersect the parent (and is therefore
-                // not displayed) we may need need to invalidate
-                needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
-                        view.getRight(), view.getBottom());
-            }
-
-            // Now offset, invoking the API 11+ implementation (which contains its own workarounds)
-            super.offsetTopAndBottom(view, offset);
-
-            // The view has now been offset, so let's intersect the Rect and invalidate where
-            // the View is now displayed
-            if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
-                    view.getRight(), view.getBottom())) {
-                ((View) parent).invalidate(parentRect);
-            }
-        }
-
-        private static Rect getEmptyTempRect() {
-            if (sThreadLocalRect == null) {
-                sThreadLocalRect = new ThreadLocal<>();
-            }
-            Rect rect = sThreadLocalRect.get();
-            if (rect == null) {
-                rect = new Rect();
-                sThreadLocalRect.set(rect);
-            }
-            rect.setEmpty();
-            return rect;
-        }
-    }
-
-    @RequiresApi(23)
-    static class ViewCompatApi23Impl extends ViewCompatApi21Impl {
-        @Override
-        public void offsetLeftAndRight(View view, int offset) {
-            view.offsetLeftAndRight(offset);
-        }
-
-        @Override
-        public void offsetTopAndBottom(View view, int offset) {
-            view.offsetTopAndBottom(offset);
-        }
-    }
-
-    static final ViewCompatBaseImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 23) {
-            IMPL = new ViewCompatApi23Impl();
-        } else if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new ViewCompatApi21Impl();
-        } else {
-            IMPL = new ViewCompatBaseImpl();
-        }
+        rect.setEmpty();
+        return rect;
     }
 
     /**
@@ -859,7 +664,7 @@
      */
     public static void setAccessibilityDelegate(@NonNull View v,
             AccessibilityDelegateCompat delegate) {
-        IMPL.setAccessibilityDelegate(v, delegate);
+        v.setAccessibilityDelegate(delegate == null ? null : delegate.getBridge());
     }
 
     /**
@@ -1039,7 +844,25 @@
      * @return True if the View has an accessibility delegate
      */
     public static boolean hasAccessibilityDelegate(@NonNull View v) {
-        return IMPL.hasAccessibilityDelegate(v);
+        if (sAccessibilityDelegateCheckFailed) {
+            return false; // View implementation might have changed.
+        }
+        if (sAccessibilityDelegateField == null) {
+            try {
+                sAccessibilityDelegateField = View.class
+                        .getDeclaredField("mAccessibilityDelegate");
+                sAccessibilityDelegateField.setAccessible(true);
+            } catch (Throwable t) {
+                sAccessibilityDelegateCheckFailed = true;
+                return false;
+            }
+        }
+        try {
+            return sAccessibilityDelegateField.get(v) != null;
+        } catch (Throwable t) {
+            sAccessibilityDelegateCheckFailed = true;
+            return false;
+        }
     }
 
     /**
@@ -1167,7 +990,7 @@
         if (Build.VERSION.SDK_INT >= 16) {
             return view.getImportantForAccessibility();
         }
-        return 0;
+        return IMPORTANT_FOR_ACCESSIBILITY_AUTO;
     }
 
     /**
@@ -1915,7 +1738,15 @@
      */
     @NonNull
     public static ViewPropertyAnimatorCompat animate(@NonNull View view) {
-        return IMPL.animate(view);
+        if (sViewPropertyAnimatorMap == null) {
+            sViewPropertyAnimatorMap = new WeakHashMap<>();
+        }
+        ViewPropertyAnimatorCompat vpa = sViewPropertyAnimatorMap.get(view);
+        if (vpa == null) {
+            vpa = new ViewPropertyAnimatorCompat(view);
+            sViewPropertyAnimatorMap.put(view, vpa);
+        }
+        return vpa;
     }
 
     /**
@@ -2226,7 +2057,14 @@
      * @param transitionName The name of the View to uniquely identify it for Transitions.
      */
     public static void setTransitionName(@NonNull View view, String transitionName) {
-        IMPL.setTransitionName(view, transitionName);
+        if (Build.VERSION.SDK_INT >= 21) {
+            view.setTransitionName(transitionName);
+        } else {
+            if (sTransitionNameMap == null) {
+                sTransitionNameMap = new WeakHashMap<>();
+            }
+            sTransitionNameMap.put(view, transitionName);
+        }
     }
 
     /**
@@ -2241,7 +2079,13 @@
      */
     @Nullable
     public static String getTransitionName(@NonNull View view) {
-        return IMPL.getTransitionName(view);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return view.getTransitionName();
+        }
+        if (sTransitionNameMap == null) {
+            return null;
+        }
+        return sTransitionNameMap.get(view);
     }
 
     /**
@@ -2279,7 +2123,24 @@
      */
     @Deprecated
     public static void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
-       IMPL.setChildrenDrawingOrderEnabled(viewGroup, enabled);
+        if (sChildrenDrawingOrderMethod == null) {
+            try {
+                sChildrenDrawingOrderMethod = ViewGroup.class
+                        .getDeclaredMethod("setChildrenDrawingOrderEnabled", boolean.class);
+            } catch (NoSuchMethodException e) {
+                Log.e(TAG, "Unable to find childrenDrawingOrderEnabled", e);
+            }
+            sChildrenDrawingOrderMethod.setAccessible(true);
+        }
+        try {
+            sChildrenDrawingOrderMethod.invoke(viewGroup, enabled);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
+        }
     }
 
     /**
@@ -3039,7 +2900,46 @@
      * @param offset the number of pixels to offset the view by
      */
     public static void offsetTopAndBottom(@NonNull View view, int offset) {
-        IMPL.offsetTopAndBottom(view, offset);
+        if (Build.VERSION.SDK_INT >= 23) {
+            view.offsetTopAndBottom(offset);
+        } else if (Build.VERSION.SDK_INT >= 21) {
+            final Rect parentRect = getEmptyTempRect();
+            boolean needInvalidateWorkaround = false;
+
+            final ViewParent parent = view.getParent();
+            if (parent instanceof View) {
+                final View p = (View) parent;
+                parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
+                // If the view currently does not currently intersect the parent (and is therefore
+                // not displayed) we may need need to invalidate
+                needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
+                        view.getRight(), view.getBottom());
+            }
+
+            // Now offset, invoking the API 14+ implementation (which contains its own workarounds)
+            compatOffsetTopAndBottom(view, offset);
+
+            // The view has now been offset, so let's intersect the Rect and invalidate where
+            // the View is now displayed
+            if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
+                    view.getRight(), view.getBottom())) {
+                ((View) parent).invalidate(parentRect);
+            }
+        } else {
+            compatOffsetTopAndBottom(view, offset);
+        }
+    }
+
+    private static void compatOffsetTopAndBottom(View view, int offset) {
+        view.offsetTopAndBottom(offset);
+        if (view.getVisibility() == View.VISIBLE) {
+            tickleInvalidationFlag(view);
+
+            ViewParent parent = view.getParent();
+            if (parent instanceof View) {
+                tickleInvalidationFlag((View) parent);
+            }
+        }
     }
 
     /**
@@ -3048,7 +2948,52 @@
      * @param offset the number of pixels to offset the view by
      */
     public static void offsetLeftAndRight(@NonNull View view, int offset) {
-        IMPL.offsetLeftAndRight(view, offset);
+        if (Build.VERSION.SDK_INT >= 23) {
+            view.offsetLeftAndRight(offset);
+        } else if (Build.VERSION.SDK_INT >= 21) {
+            final Rect parentRect = getEmptyTempRect();
+            boolean needInvalidateWorkaround = false;
+
+            final ViewParent parent = view.getParent();
+            if (parent instanceof View) {
+                final View p = (View) parent;
+                parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
+                // If the view currently does not currently intersect the parent (and is therefore
+                // not displayed) we may need need to invalidate
+                needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
+                        view.getRight(), view.getBottom());
+            }
+
+            // Now offset, invoking the API 14+ implementation (which contains its own workarounds)
+            compatOffsetLeftAndRight(view, offset);
+
+            // The view has now been offset, so let's intersect the Rect and invalidate where
+            // the View is now displayed
+            if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
+                    view.getRight(), view.getBottom())) {
+                ((View) parent).invalidate(parentRect);
+            }
+        } else {
+            compatOffsetLeftAndRight(view, offset);
+        }
+    }
+
+    private static void compatOffsetLeftAndRight(View view, int offset) {
+        view.offsetLeftAndRight(offset);
+        if (view.getVisibility() == View.VISIBLE) {
+            tickleInvalidationFlag(view);
+
+            ViewParent parent = view.getParent();
+            if (parent instanceof View) {
+                tickleInvalidationFlag((View) parent);
+            }
+        }
+    }
+
+    private static void tickleInvalidationFlag(View view) {
+        final float y = view.getTranslationY();
+        view.setTranslationY(y + 1);
+        view.setTranslationY(y);
     }
 
     /**
diff --git a/compat/src/main/java/androidx/core/view/ViewGroupCompat.java b/compat/src/main/java/androidx/core/view/ViewGroupCompat.java
index bd1d0ed..7da7247 100644
--- a/compat/src/main/java/androidx/core/view/ViewGroupCompat.java
+++ b/compat/src/main/java/androidx/core/view/ViewGroupCompat.java
@@ -22,7 +22,6 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 import androidx.core.R;
 import androidx.core.view.ViewCompat.ScrollAxis;
 
@@ -47,76 +46,6 @@
      */
     public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
 
-    static class ViewGroupCompatBaseImpl {
-        public int getLayoutMode(ViewGroup group) {
-            return LAYOUT_MODE_CLIP_BOUNDS;
-        }
-
-        public void setLayoutMode(ViewGroup group, int mode) {
-            // no-op, didn't exist. Views only support clip bounds.
-        }
-
-        public void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
-            group.setTag(R.id.tag_transition_group, isTransitionGroup);
-        }
-
-        public boolean isTransitionGroup(ViewGroup group) {
-            Boolean explicit = (Boolean) group.getTag(R.id.tag_transition_group);
-            return (explicit != null && explicit)
-                    || group.getBackground() != null
-                    || ViewCompat.getTransitionName(group) != null;
-        }
-
-        public int getNestedScrollAxes(ViewGroup group) {
-            if (group instanceof NestedScrollingParent) {
-                return ((NestedScrollingParent) group).getNestedScrollAxes();
-            }
-            return ViewCompat.SCROLL_AXIS_NONE;
-        }
-    }
-
-    @RequiresApi(18)
-    static class ViewGroupCompatApi18Impl extends ViewGroupCompatBaseImpl {
-        @Override
-        public int getLayoutMode(ViewGroup group) {
-            return group.getLayoutMode();
-        }
-
-        @Override
-        public void setLayoutMode(ViewGroup group, int mode) {
-            group.setLayoutMode(mode);
-        }
-    }
-
-    @RequiresApi(21)
-    static class ViewGroupCompatApi21Impl extends ViewGroupCompatApi18Impl {
-        @Override
-        public void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
-            group.setTransitionGroup(isTransitionGroup);
-        }
-
-        @Override
-        public boolean isTransitionGroup(ViewGroup group) {
-            return group.isTransitionGroup();
-        }
-
-        @Override
-        public int getNestedScrollAxes(ViewGroup group) {
-            return group.getNestedScrollAxes();
-        }
-    }
-
-    static final ViewGroupCompatBaseImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new ViewGroupCompatApi21Impl();
-        } else if (Build.VERSION.SDK_INT >= 18) {
-            IMPL = new ViewGroupCompatApi18Impl();
-        } else {
-            IMPL = new ViewGroupCompatBaseImpl();
-        }
-    }
-
     /*
      * Hide the constructor.
      */
@@ -182,7 +111,10 @@
      * @see #setLayoutMode(ViewGroup, int)
      */
     public static int getLayoutMode(@NonNull ViewGroup group) {
-        return IMPL.getLayoutMode(group);
+        if (Build.VERSION.SDK_INT >= 18) {
+            return group.getLayoutMode();
+        }
+        return LAYOUT_MODE_CLIP_BOUNDS;
     }
 
     /**
@@ -195,7 +127,9 @@
      * @see #getLayoutMode(ViewGroup)
      */
     public static void setLayoutMode(@NonNull ViewGroup group, int mode) {
-        IMPL.setLayoutMode(group, mode);
+        if (Build.VERSION.SDK_INT >= 18) {
+            group.setLayoutMode(mode);
+        }
     }
 
     /**
@@ -207,7 +141,11 @@
      *                          together.
      */
     public static void setTransitionGroup(@NonNull ViewGroup group, boolean isTransitionGroup) {
-        IMPL.setTransitionGroup(group, isTransitionGroup);
+        if (Build.VERSION.SDK_INT >= 21) {
+            group.setTransitionGroup(isTransitionGroup);
+        } else {
+            group.setTag(R.id.tag_transition_group, isTransitionGroup);
+        }
     }
 
     /**
@@ -216,7 +154,13 @@
      * individually during the transition.
      */
     public static boolean isTransitionGroup(@NonNull ViewGroup group) {
-        return IMPL.isTransitionGroup(group);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return group.isTransitionGroup();
+        }
+        Boolean explicit = (Boolean) group.getTag(R.id.tag_transition_group);
+        return (explicit != null && explicit)
+                || group.getBackground() != null
+                || ViewCompat.getTransitionName(group) != null;
     }
 
     /**
@@ -232,7 +176,14 @@
      * @see ViewCompat#SCROLL_AXIS_NONE
      */
     @ScrollAxis
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static int getNestedScrollAxes(@NonNull ViewGroup group) {
-        return IMPL.getNestedScrollAxes(group);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return group.getNestedScrollAxes();
+        }
+        if (group instanceof NestedScrollingParent) {
+            return ((NestedScrollingParent) group).getNestedScrollAxes();
+        }
+        return ViewCompat.SCROLL_AXIS_NONE;
     }
 }
diff --git a/compat/src/main/java/androidx/core/view/ViewParentCompat.java b/compat/src/main/java/androidx/core/view/ViewParentCompat.java
index 1786ee3..5ad38cc 100644
--- a/compat/src/main/java/androidx/core/view/ViewParentCompat.java
+++ b/compat/src/main/java/androidx/core/view/ViewParentCompat.java
@@ -25,8 +25,6 @@
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 
-import androidx.annotation.RequiresApi;
-
 /**
  * Helper for accessing features in {@link ViewParent}.
  */
@@ -34,171 +32,6 @@
 
     private static final String TAG = "ViewParentCompat";
 
-    static class ViewParentCompatBaseImpl {
-        public boolean onStartNestedScroll(ViewParent parent, View child, View target,
-                int nestedScrollAxes) {
-            if (parent instanceof NestedScrollingParent) {
-                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
-                        nestedScrollAxes);
-            }
-            return false;
-        }
-
-        public void onNestedScrollAccepted(ViewParent parent, View child, View target,
-                int nestedScrollAxes) {
-            if (parent instanceof NestedScrollingParent) {
-                ((NestedScrollingParent) parent).onNestedScrollAccepted(child, target,
-                        nestedScrollAxes);
-            }
-        }
-
-        public void onStopNestedScroll(ViewParent parent, View target) {
-            if (parent instanceof NestedScrollingParent) {
-                ((NestedScrollingParent) parent).onStopNestedScroll(target);
-            }
-        }
-
-        public void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed,
-                int dxUnconsumed, int dyUnconsumed) {
-            if (parent instanceof NestedScrollingParent) {
-                ((NestedScrollingParent) parent).onNestedScroll(target, dxConsumed, dyConsumed,
-                        dxUnconsumed, dyUnconsumed);
-            }
-        }
-
-        public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
-                int[] consumed) {
-            if (parent instanceof NestedScrollingParent) {
-                ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);
-            }
-        }
-
-        public boolean onNestedFling(ViewParent parent, View target, float velocityX,
-                float velocityY, boolean consumed) {
-            if (parent instanceof NestedScrollingParent) {
-                return ((NestedScrollingParent) parent).onNestedFling(target, velocityX, velocityY,
-                        consumed);
-            }
-            return false;
-        }
-
-        public boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
-                float velocityY) {
-            if (parent instanceof NestedScrollingParent) {
-                return ((NestedScrollingParent) parent).onNestedPreFling(target, velocityX,
-                        velocityY);
-            }
-            return false;
-        }
-
-        public void notifySubtreeAccessibilityStateChanged(ViewParent parent, View child,
-                View source, int changeType) {
-        }
-    }
-
-    @RequiresApi(19)
-    static class ViewParentCompatApi19Impl extends ViewParentCompatBaseImpl {
-
-        @Override
-        public void notifySubtreeAccessibilityStateChanged(ViewParent parent, View child,
-                View source, int changeType) {
-            parent.notifySubtreeAccessibilityStateChanged(child, source, changeType);
-        }
-    }
-
-    @RequiresApi(21)
-    static class ViewParentCompatApi21Impl extends ViewParentCompatApi19Impl {
-        @Override
-        public boolean onStartNestedScroll(ViewParent parent, View child, View target,
-                int nestedScrollAxes) {
-            try {
-                return parent.onStartNestedScroll(child, target, nestedScrollAxes);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
-                        + "method onStartNestedScroll", e);
-                return false;
-            }
-        }
-
-        @Override
-        public void onNestedScrollAccepted(ViewParent parent, View child, View target,
-                int nestedScrollAxes) {
-            try {
-                parent.onNestedScrollAccepted(child, target, nestedScrollAxes);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
-                        + "method onNestedScrollAccepted", e);
-            }
-        }
-
-        @Override
-        public void onStopNestedScroll(ViewParent parent, View target) {
-            try {
-                parent.onStopNestedScroll(target);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
-                        + "method onStopNestedScroll", e);
-            }
-        }
-
-        @Override
-        public void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed,
-                int dxUnconsumed, int dyUnconsumed) {
-            try {
-                parent.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
-                        + "method onNestedScroll", e);
-            }
-        }
-
-        @Override
-        public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
-                int[] consumed) {
-            try {
-                parent.onNestedPreScroll(target, dx, dy, consumed);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
-                        + "method onNestedPreScroll", e);
-            }
-        }
-
-        @Override
-        public boolean onNestedFling(ViewParent parent, View target, float velocityX,
-                float velocityY, boolean consumed) {
-            try {
-                return parent.onNestedFling(target, velocityX, velocityY, consumed);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
-                        + "method onNestedFling", e);
-                return false;
-            }
-        }
-
-        @Override
-        public boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
-                float velocityY) {
-            try {
-                return parent.onNestedPreFling(target, velocityX, velocityY);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
-                        + "method onNestedPreFling", e);
-                return false;
-            }
-        }
-    }
-
-    static final ViewParentCompatBaseImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new ViewParentCompatApi21Impl();
-        } else if (Build.VERSION.SDK_INT >= 19) {
-            IMPL = new ViewParentCompatApi19Impl();
-        } else {
-            IMPL = new ViewParentCompatBaseImpl();
-        }
-    }
-
     /*
      * Hide the constructor.
      */
@@ -337,6 +170,7 @@
      * @param type the type of input which cause this scroll event
      * @return true if this ViewParent accepts the nested scroll operation
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
             int nestedScrollAxes, int type) {
         if (parent instanceof NestedScrollingParent2) {
@@ -345,7 +179,17 @@
                     nestedScrollAxes, type);
         } else if (type == ViewCompat.TYPE_TOUCH) {
             // Else if the type is the default (touch), try the NestedScrollingParent API
-            return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
+            if (Build.VERSION.SDK_INT >= 21) {
+                try {
+                    return parent.onStartNestedScroll(child, target, nestedScrollAxes);
+                } catch (AbstractMethodError e) {
+                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
+                            + "method onStartNestedScroll", e);
+                }
+            } else if (parent instanceof NestedScrollingParent) {
+                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
+                        nestedScrollAxes);
+            }
         }
         return false;
     }
@@ -367,6 +211,7 @@
      * @see #onStartNestedScroll(ViewParent, View, View, int)
      * @see #onStopNestedScroll(ViewParent, View, int)
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static void onNestedScrollAccepted(ViewParent parent, View child, View target,
             int nestedScrollAxes, int type) {
         if (parent instanceof NestedScrollingParent2) {
@@ -375,7 +220,17 @@
                     nestedScrollAxes, type);
         } else if (type == ViewCompat.TYPE_TOUCH) {
             // Else if the type is the default (touch), try the NestedScrollingParent API
-            IMPL.onNestedScrollAccepted(parent, child, target, nestedScrollAxes);
+            if (Build.VERSION.SDK_INT >= 21) {
+                try {
+                    parent.onNestedScrollAccepted(child, target, nestedScrollAxes);
+                } catch (AbstractMethodError e) {
+                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
+                            + "method onNestedScrollAccepted", e);
+                }
+            } else if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onNestedScrollAccepted(child, target,
+                        nestedScrollAxes);
+            }
         }
     }
 
@@ -391,13 +246,23 @@
      * @param target View that initiated the nested scroll
      * @param type the type of input which cause this scroll event
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static void onStopNestedScroll(ViewParent parent, View target, int type) {
         if (parent instanceof NestedScrollingParent2) {
             // First try the NestedScrollingParent2 API
             ((NestedScrollingParent2) parent).onStopNestedScroll(target, type);
         } else if (type == ViewCompat.TYPE_TOUCH) {
             // Else if the type is the default (touch), try the NestedScrollingParent API
-            IMPL.onStopNestedScroll(parent, target);
+            if (Build.VERSION.SDK_INT >= 21) {
+                try {
+                    parent.onStopNestedScroll(target);
+                } catch (AbstractMethodError e) {
+                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
+                            + "method onStopNestedScroll", e);
+                }
+            } else if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onStopNestedScroll(target);
+            }
         }
     }
 
@@ -423,6 +288,7 @@
      * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target
      * @param type the type of input which cause this scroll event
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static void onNestedScroll(ViewParent parent, View target, int dxConsumed,
             int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
         if (parent instanceof NestedScrollingParent2) {
@@ -431,7 +297,18 @@
                     dxUnconsumed, dyUnconsumed, type);
         } else if (type == ViewCompat.TYPE_TOUCH) {
             // Else if the type is the default (touch), try the NestedScrollingParent API
-            IMPL.onNestedScroll(parent, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
+            if (Build.VERSION.SDK_INT >= 21) {
+                try {
+                    parent.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed,
+                            dyUnconsumed);
+                } catch (AbstractMethodError e) {
+                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
+                            + "method onNestedScroll", e);
+                }
+            } else if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onNestedScroll(target, dxConsumed, dyConsumed,
+                        dxUnconsumed, dyUnconsumed);
+            }
         }
     }
 
@@ -456,6 +333,7 @@
      * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
      * @param type the type of input which cause this scroll event
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
             int[] consumed, int type) {
         if (parent instanceof NestedScrollingParent2) {
@@ -463,7 +341,16 @@
             ((NestedScrollingParent2) parent).onNestedPreScroll(target, dx, dy, consumed, type);
         } else if (type == ViewCompat.TYPE_TOUCH) {
             // Else if the type is the default (touch), try the NestedScrollingParent API
-            IMPL.onNestedPreScroll(parent, target, dx, dy, consumed);
+            if (Build.VERSION.SDK_INT >= 21) {
+                try {
+                    parent.onNestedPreScroll(target, dx, dy, consumed);
+                } catch (AbstractMethodError e) {
+                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
+                            + "method onNestedPreScroll", e);
+                }
+            } else if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);
+            }
         }
     }
 
@@ -486,9 +373,21 @@
      * @param consumed true if the child consumed the fling, false otherwise
      * @return true if this parent consumed or otherwise reacted to the fling
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static boolean onNestedFling(ViewParent parent, View target, float velocityX,
             float velocityY, boolean consumed) {
-        return IMPL.onNestedFling(parent, target, velocityX, velocityY, consumed);
+        if (Build.VERSION.SDK_INT >= 21) {
+            try {
+                return parent.onNestedFling(target, velocityX, velocityY, consumed);
+            } catch (AbstractMethodError e) {
+                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
+                        + "method onNestedFling", e);
+            }
+        } else if (parent instanceof NestedScrollingParent) {
+            return ((NestedScrollingParent) parent).onNestedFling(target, velocityX, velocityY,
+                    consumed);
+        }
+        return false;
     }
 
     /**
@@ -511,9 +410,21 @@
      * @param velocityY Vertical velocity in pixels per second
      * @return true if this parent consumed the fling ahead of the target view
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
             float velocityY) {
-        return IMPL.onNestedPreFling(parent, target, velocityX, velocityY);
+        if (Build.VERSION.SDK_INT >= 21) {
+            try {
+                return parent.onNestedPreFling(target, velocityX, velocityY);
+            } catch (AbstractMethodError e) {
+                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
+                        + "method onNestedPreFling", e);
+            }
+        } else if (parent instanceof NestedScrollingParent) {
+            return ((NestedScrollingParent) parent).onNestedPreFling(target, velocityX,
+                    velocityY);
+        }
+        return false;
     }
 
     /**
@@ -533,6 +444,8 @@
      */
     public static void notifySubtreeAccessibilityStateChanged(ViewParent parent, View child,
             View source, int changeType) {
-        IMPL.notifySubtreeAccessibilityStateChanged(parent, child, source, changeType);
+        if (Build.VERSION.SDK_INT >= 19) {
+            parent.notifySubtreeAccessibilityStateChanged(child, source, changeType);
+        }
     }
 }
diff --git a/compat/src/main/java/androidx/core/widget/CompoundButtonCompat.java b/compat/src/main/java/androidx/core/widget/CompoundButtonCompat.java
index 519f98a..ccbf0df 100644
--- a/compat/src/main/java/androidx/core/widget/CompoundButtonCompat.java
+++ b/compat/src/main/java/androidx/core/widget/CompoundButtonCompat.java
@@ -25,7 +25,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.core.graphics.drawable.DrawableCompat;
 
 import java.lang.reflect.Field;
@@ -34,104 +33,10 @@
  * Helper for accessing {@link android.widget.CompoundButton}.
  */
 public final class CompoundButtonCompat {
+    private static final String TAG = "CompoundButtonCompat";
 
-    private static final CompoundButtonCompatBaseImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 23) {
-            IMPL = new CompoundButtonCompatApi23Impl();
-        } else if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new CompoundButtonCompatApi21Impl();
-        } else {
-            IMPL = new CompoundButtonCompatBaseImpl();
-        }
-    }
-
-    static class CompoundButtonCompatBaseImpl {
-        private static final String TAG = "CompoundButtonCompat";
-
-        private static Field sButtonDrawableField;
-        private static boolean sButtonDrawableFieldFetched;
-
-        public void setButtonTintList(CompoundButton button, ColorStateList tint) {
-            if (button instanceof TintableCompoundButton) {
-                ((TintableCompoundButton) button).setSupportButtonTintList(tint);
-            }
-        }
-
-        public ColorStateList getButtonTintList(CompoundButton button) {
-            if (button instanceof TintableCompoundButton) {
-                return ((TintableCompoundButton) button).getSupportButtonTintList();
-            }
-            return null;
-        }
-
-        public void setButtonTintMode(CompoundButton button, PorterDuff.Mode tintMode) {
-            if (button instanceof TintableCompoundButton) {
-                ((TintableCompoundButton) button).setSupportButtonTintMode(tintMode);
-            }
-        }
-
-        public PorterDuff.Mode getButtonTintMode(CompoundButton button) {
-            if (button instanceof TintableCompoundButton) {
-                return ((TintableCompoundButton) button).getSupportButtonTintMode();
-            }
-            return null;
-        }
-
-        public Drawable getButtonDrawable(CompoundButton button) {
-            if (!sButtonDrawableFieldFetched) {
-                try {
-                    sButtonDrawableField = CompoundButton.class.getDeclaredField("mButtonDrawable");
-                    sButtonDrawableField.setAccessible(true);
-                } catch (NoSuchFieldException e) {
-                    Log.i(TAG, "Failed to retrieve mButtonDrawable field", e);
-                }
-                sButtonDrawableFieldFetched = true;
-            }
-
-            if (sButtonDrawableField != null) {
-                try {
-                    return (Drawable) sButtonDrawableField.get(button);
-                } catch (IllegalAccessException e) {
-                    Log.i(TAG, "Failed to get button drawable via reflection", e);
-                    sButtonDrawableField = null;
-                }
-            }
-            return null;
-        }
-    }
-
-    @RequiresApi(21)
-    static class CompoundButtonCompatApi21Impl extends CompoundButtonCompatBaseImpl {
-        @Override
-        public void setButtonTintList(CompoundButton button, ColorStateList tint) {
-            button.setButtonTintList(tint);
-        }
-
-        @Override
-        public ColorStateList getButtonTintList(CompoundButton button) {
-            return button.getButtonTintList();
-        }
-
-        @Override
-        public void setButtonTintMode(CompoundButton button, PorterDuff.Mode tintMode) {
-            button.setButtonTintMode(tintMode);
-        }
-
-        @Override
-        public PorterDuff.Mode getButtonTintMode(CompoundButton button) {
-            return button.getButtonTintMode();
-        }
-    }
-
-    @RequiresApi(23)
-    static class CompoundButtonCompatApi23Impl extends CompoundButtonCompatApi21Impl {
-        @Override
-        public Drawable getButtonDrawable(CompoundButton button) {
-            return button.getButtonDrawable();
-        }
-    }
+    private static Field sButtonDrawableField;
+    private static boolean sButtonDrawableFieldFetched;
 
     private CompoundButtonCompat() {}
 
@@ -147,8 +52,13 @@
      *
      * @see #setButtonTintList(CompoundButton, ColorStateList)
      */
-    public static void setButtonTintList(@NonNull CompoundButton button, @Nullable ColorStateList tint) {
-        IMPL.setButtonTintList(button, tint);
+    public static void setButtonTintList(@NonNull CompoundButton button,
+            @Nullable ColorStateList tint) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            button.setButtonTintList(tint);
+        } else if (button instanceof TintableCompoundButton) {
+            ((TintableCompoundButton) button).setSupportButtonTintList(tint);
+        }
     }
 
     /**
@@ -158,7 +68,13 @@
      */
     @Nullable
     public static ColorStateList getButtonTintList(@NonNull CompoundButton button) {
-        return IMPL.getButtonTintList(button);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return button.getButtonTintList();
+        }
+        if (button instanceof TintableCompoundButton) {
+            return ((TintableCompoundButton) button).getSupportButtonTintList();
+        }
+        return null;
     }
 
     /**
@@ -174,7 +90,11 @@
      */
     public static void setButtonTintMode(@NonNull CompoundButton button,
             @Nullable PorterDuff.Mode tintMode) {
-        IMPL.setButtonTintMode(button, tintMode);
+        if (Build.VERSION.SDK_INT >= 21) {
+            button.setButtonTintMode(tintMode);
+        } else if (button instanceof TintableCompoundButton) {
+            ((TintableCompoundButton) button).setSupportButtonTintMode(tintMode);
+        }
     }
 
     /**
@@ -184,7 +104,13 @@
      */
     @Nullable
     public static PorterDuff.Mode getButtonTintMode(@NonNull CompoundButton button) {
-        return IMPL.getButtonTintMode(button);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return button.getButtonTintMode();
+        }
+        if (button instanceof TintableCompoundButton) {
+            return ((TintableCompoundButton) button).getSupportButtonTintMode();
+        }
+        return null;
     }
 
     /**
@@ -194,6 +120,28 @@
      */
     @Nullable
     public static Drawable getButtonDrawable(@NonNull CompoundButton button) {
-        return IMPL.getButtonDrawable(button);
+        if (Build.VERSION.SDK_INT >= 23) {
+            return button.getButtonDrawable();
+        }
+
+        if (!sButtonDrawableFieldFetched) {
+            try {
+                sButtonDrawableField = CompoundButton.class.getDeclaredField("mButtonDrawable");
+                sButtonDrawableField.setAccessible(true);
+            } catch (NoSuchFieldException e) {
+                Log.i(TAG, "Failed to retrieve mButtonDrawable field", e);
+            }
+            sButtonDrawableFieldFetched = true;
+        }
+
+        if (sButtonDrawableField != null) {
+            try {
+                return (Drawable) sButtonDrawableField.get(button);
+            } catch (IllegalAccessException e) {
+                Log.i(TAG, "Failed to get button drawable via reflection", e);
+                sButtonDrawableField = null;
+            }
+        }
+        return null;
     }
 }
diff --git a/compat/src/main/java/androidx/core/widget/ImageViewCompat.java b/compat/src/main/java/androidx/core/widget/ImageViewCompat.java
index ce93724..3ec5b88 100644
--- a/compat/src/main/java/androidx/core/widget/ImageViewCompat.java
+++ b/compat/src/main/java/androidx/core/widget/ImageViewCompat.java
@@ -24,61 +24,30 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 /**
  * Helper for accessing features in {@link ImageView}.
  */
 public class ImageViewCompat {
-    interface ImageViewCompatImpl {
-        ColorStateList getImageTintList(ImageView view);
-
-        void setImageTintList(ImageView view, ColorStateList tintList);
-
-        PorterDuff.Mode getImageTintMode(ImageView view);
-
-        void setImageTintMode(ImageView view, PorterDuff.Mode mode);
-    }
-
-    static class BaseViewCompatImpl implements ImageViewCompatImpl {
-        @Override
-        public ColorStateList getImageTintList(ImageView view) {
-            return (view instanceof TintableImageSourceView)
-                    ? ((TintableImageSourceView) view).getSupportImageTintList()
-                    : null;
-        }
-
-        @Override
-        public void setImageTintList(ImageView view, ColorStateList tintList) {
-            if (view instanceof TintableImageSourceView) {
-                ((TintableImageSourceView) view).setSupportImageTintList(tintList);
-            }
-        }
-
-        @Override
-        public void setImageTintMode(ImageView view, PorterDuff.Mode mode) {
-            if (view instanceof TintableImageSourceView) {
-                ((TintableImageSourceView) view).setSupportImageTintMode(mode);
-            }
-        }
-
-        @Override
-        public PorterDuff.Mode getImageTintMode(ImageView view) {
-            return (view instanceof TintableImageSourceView)
-                    ? ((TintableImageSourceView) view).getSupportImageTintMode()
-                    : null;
-        }
-    }
-
-    @RequiresApi(21)
-    static class LollipopViewCompatImpl extends BaseViewCompatImpl {
-        @Override
-        public ColorStateList getImageTintList(ImageView view) {
+    /**
+     * Return the tint applied to the image drawable, if specified.
+     */
+    @Nullable
+    public static ColorStateList getImageTintList(@NonNull ImageView view) {
+        if (Build.VERSION.SDK_INT >= 21) {
             return view.getImageTintList();
         }
+        return (view instanceof TintableImageSourceView)
+                ? ((TintableImageSourceView) view).getSupportImageTintList()
+                : null;
+    }
 
-        @Override
-        public void setImageTintList(ImageView view, ColorStateList tintList) {
+    /**
+     * Applies a tint to the image drawable.
+     */
+    public static void setImageTintList(@NonNull ImageView view,
+            @Nullable ColorStateList tintList) {
+        if (Build.VERSION.SDK_INT >= 21) {
             view.setImageTintList(tintList);
 
             if (Build.VERSION.SDK_INT == 21) {
@@ -94,10 +63,31 @@
                     view.setImageDrawable(imageViewDrawable);
                 }
             }
+        } else if (view instanceof TintableImageSourceView) {
+            ((TintableImageSourceView) view).setSupportImageTintList(tintList);
         }
+    }
 
-        @Override
-        public void setImageTintMode(ImageView view, PorterDuff.Mode mode) {
+    /**
+     * Return the blending mode used to apply the tint to the image drawable, if specified.
+     */
+    @Nullable
+    public static PorterDuff.Mode getImageTintMode(@NonNull ImageView view) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            return view.getImageTintMode();
+        }
+        return (view instanceof TintableImageSourceView)
+                ? ((TintableImageSourceView) view).getSupportImageTintMode()
+                : null;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setImageTintList(android.widget.ImageView, android.content.res.ColorStateList)}
+     * to the image drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+     */
+    public static void setImageTintMode(@NonNull ImageView view, @Nullable PorterDuff.Mode mode) {
+        if (Build.VERSION.SDK_INT >= 21) {
             view.setImageTintMode(mode);
 
             if (Build.VERSION.SDK_INT == 21) {
@@ -113,54 +103,9 @@
                     view.setImageDrawable(imageViewDrawable);
                 }
             }
+        } else if (view instanceof TintableImageSourceView) {
+            ((TintableImageSourceView) view).setSupportImageTintMode(mode);
         }
-
-        @Override
-        public PorterDuff.Mode getImageTintMode(ImageView view) {
-            return view.getImageTintMode();
-        }
-    }
-
-    static final ImageViewCompatImpl IMPL;
-    static {
-        if (android.os.Build.VERSION.SDK_INT >= 21) {
-            IMPL = new LollipopViewCompatImpl();
-        } else {
-            IMPL = new BaseViewCompatImpl();
-        }
-    }
-
-    /**
-     * Return the tint applied to the image drawable, if specified.
-     */
-    @Nullable
-    public static ColorStateList getImageTintList(@NonNull ImageView view) {
-        return IMPL.getImageTintList(view);
-    }
-
-    /**
-     * Applies a tint to the image drawable.
-     */
-    public static void setImageTintList(@NonNull ImageView view,
-            @Nullable ColorStateList tintList) {
-        IMPL.setImageTintList(view, tintList);
-    }
-
-    /**
-     * Return the blending mode used to apply the tint to the image drawable, if specified.
-     */
-    @Nullable
-    public static PorterDuff.Mode getImageTintMode(@NonNull ImageView view) {
-        return IMPL.getImageTintMode(view);
-    }
-
-    /**
-     * Specifies the blending mode used to apply the tint specified by
-     * {@link #setImageTintList(android.widget.ImageView, android.content.res.ColorStateList)}
-     * to the image drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
-     */
-    public static void setImageTintMode(@NonNull ImageView view, @Nullable PorterDuff.Mode mode) {
-        IMPL.setImageTintMode(view, mode);
     }
 
     private ImageViewCompat() {}
diff --git a/compat/src/main/java/androidx/core/widget/NestedScrollView.java b/compat/src/main/java/androidx/core/widget/NestedScrollView.java
index 2490bd4..307f32f 100644
--- a/compat/src/main/java/androidx/core/widget/NestedScrollView.java
+++ b/compat/src/main/java/androidx/core/widget/NestedScrollView.java
@@ -52,7 +52,7 @@
 import androidx.core.view.InputDeviceCompat;
 import androidx.core.view.NestedScrollingChild2;
 import androidx.core.view.NestedScrollingChildHelper;
-import androidx.core.view.NestedScrollingParent;
+import androidx.core.view.NestedScrollingParent2;
 import androidx.core.view.NestedScrollingParentHelper;
 import androidx.core.view.ScrollingView;
 import androidx.core.view.ViewCompat;
@@ -66,7 +66,7 @@
  * as both a nested scrolling parent and child on both new and old versions of Android.
  * Nested scrolling is enabled by default.
  */
-public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
+public class NestedScrollView extends FrameLayout implements NestedScrollingParent2,
         NestedScrollingChild2, ScrollingView {
     static final int ANIMATED_SCROLL_GAP = 250;
 
@@ -214,6 +214,36 @@
         ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE);
     }
 
+    // NestedScrollingChild2
+
+    @Override
+    public boolean startNestedScroll(int axes, int type) {
+        return mChildHelper.startNestedScroll(axes, type);
+    }
+
+    @Override
+    public void stopNestedScroll(int type) {
+        mChildHelper.stopNestedScroll(type);
+    }
+
+    @Override
+    public boolean hasNestedScrollingParent(int type) {
+        return mChildHelper.hasNestedScrollingParent(type);
+    }
+
+    @Override
+    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
+            int dyUnconsumed, int[] offsetInWindow, int type) {
+        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+                offsetInWindow, type);
+    }
+
+    @Override
+    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
+            int type) {
+        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
+    }
+
     // NestedScrollingChild
 
     @Override
@@ -228,57 +258,29 @@
 
     @Override
     public boolean startNestedScroll(int axes) {
-        return mChildHelper.startNestedScroll(axes);
-    }
-
-    @Override
-    public boolean startNestedScroll(int axes, int type) {
-        return mChildHelper.startNestedScroll(axes, type);
+        return startNestedScroll(axes, ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public void stopNestedScroll() {
-        mChildHelper.stopNestedScroll();
-    }
-
-    @Override
-    public void stopNestedScroll(int type) {
-        mChildHelper.stopNestedScroll(type);
+        stopNestedScroll(ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public boolean hasNestedScrollingParent() {
-        return mChildHelper.hasNestedScrollingParent();
-    }
-
-    @Override
-    public boolean hasNestedScrollingParent(int type) {
-        return mChildHelper.hasNestedScrollingParent(type);
+        return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
             int dyUnconsumed, int[] offsetInWindow) {
-        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
-                offsetInWindow);
-    }
-
-    @Override
-    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
-            int dyUnconsumed, int[] offsetInWindow, int type) {
-        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
-                offsetInWindow, type);
+        return dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+                offsetInWindow, ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
-        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
-    }
-
-    @Override
-    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
-            int type) {
-        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
+        return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH);
     }
 
     @Override
@@ -291,38 +293,71 @@
         return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
     }
 
+    // NestedScrollingParent2
+
+    @Override
+    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
+            int type) {
+        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
+    }
+
+    @Override
+    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes,
+            int type) {
+        mParentHelper.onNestedScrollAccepted(child, target, axes, type);
+        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type);
+    }
+
+    @Override
+    public void onStopNestedScroll(@NonNull View target, int type) {
+        mParentHelper.onStopNestedScroll(target, type);
+        stopNestedScroll(type);
+    }
+
+    @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
+            int dyUnconsumed, int type) {
+        final int oldScrollY = getScrollY();
+        scrollBy(0, dyUnconsumed);
+        final int myConsumed = getScrollY() - oldScrollY;
+        final int myUnconsumed = dyUnconsumed - myConsumed;
+        dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null,
+                type);
+    }
+
+    @Override
+    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
+            int type) {
+        dispatchNestedPreScroll(dx, dy, consumed, null, type);
+    }
+
     // NestedScrollingParent
 
     @Override
     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
-        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
+        return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
-        mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
-        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
+        onNestedScrollAccepted(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public void onStopNestedScroll(View target) {
-        mParentHelper.onStopNestedScroll(target);
-        stopNestedScroll();
+        onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
             int dyUnconsumed) {
-        final int oldScrollY = getScrollY();
-        scrollBy(0, dyUnconsumed);
-        final int myConsumed = getScrollY() - oldScrollY;
-        final int myUnconsumed = dyUnconsumed - myConsumed;
-        dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
+        onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+                ViewCompat.TYPE_TOUCH);
     }
 
     @Override
     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
-        dispatchNestedPreScroll(dx, dy, consumed, null);
+        onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
     }
 
     @Override
diff --git a/compat/src/main/java/androidx/core/widget/PopupWindowCompat.java b/compat/src/main/java/androidx/core/widget/PopupWindowCompat.java
index cc97ffa..6351055 100644
--- a/compat/src/main/java/androidx/core/widget/PopupWindowCompat.java
+++ b/compat/src/main/java/androidx/core/widget/PopupWindowCompat.java
@@ -23,7 +23,6 @@
 import android.widget.PopupWindow;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 import androidx.core.view.GravityCompat;
 import androidx.core.view.ViewCompat;
 
@@ -34,166 +33,15 @@
  * Helper for accessing features in {@link PopupWindow}.
  */
 public final class PopupWindowCompat {
+    private static final String TAG = "PopupWindowCompatApi21";
 
-    static class PopupWindowCompatBaseImpl {
-        private static Method sSetWindowLayoutTypeMethod;
-        private static boolean sSetWindowLayoutTypeMethodAttempted;
-        private static Method sGetWindowLayoutTypeMethod;
-        private static boolean sGetWindowLayoutTypeMethodAttempted;
+    private static Method sSetWindowLayoutTypeMethod;
+    private static boolean sSetWindowLayoutTypeMethodAttempted;
+    private static Method sGetWindowLayoutTypeMethod;
+    private static boolean sGetWindowLayoutTypeMethodAttempted;
 
-        public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
-                int gravity) {
-            final int hgrav = GravityCompat.getAbsoluteGravity(gravity,
-                    ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK;
-            if (hgrav == Gravity.RIGHT) {
-                // Flip the location to align the right sides of the popup and
-                // anchor instead of left.
-                xoff -= (popup.getWidth() - anchor.getWidth());
-            }
-            popup.showAsDropDown(anchor, xoff, yoff);
-        }
-
-        public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
-            // noop
-        }
-
-        public boolean getOverlapAnchor(PopupWindow popupWindow) {
-            return false;
-        }
-
-        public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) {
-            if (!sSetWindowLayoutTypeMethodAttempted) {
-                try {
-                    sSetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
-                            "setWindowLayoutType", int.class);
-                    sSetWindowLayoutTypeMethod.setAccessible(true);
-                } catch (Exception e) {
-                    // Reflection method fetch failed. Oh well.
-                }
-                sSetWindowLayoutTypeMethodAttempted = true;
-            }
-
-            if (sSetWindowLayoutTypeMethod != null) {
-                try {
-                    sSetWindowLayoutTypeMethod.invoke(popupWindow, layoutType);
-                } catch (Exception e) {
-                    // Reflection call failed. Oh well.
-                }
-            }
-        }
-
-        public int getWindowLayoutType(PopupWindow popupWindow) {
-            if (!sGetWindowLayoutTypeMethodAttempted) {
-                try {
-                    sGetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
-                            "getWindowLayoutType");
-                    sGetWindowLayoutTypeMethod.setAccessible(true);
-                } catch (Exception e) {
-                    // Reflection method fetch failed. Oh well.
-                }
-                sGetWindowLayoutTypeMethodAttempted = true;
-            }
-
-            if (sGetWindowLayoutTypeMethod != null) {
-                try {
-                    return (Integer) sGetWindowLayoutTypeMethod.invoke(popupWindow);
-                } catch (Exception e) {
-                    // Reflection call failed. Oh well.
-                }
-            }
-            return 0;
-        }
-    }
-
-    /**
-     * Interface implementation for devices with at least KitKat APIs.
-     */
-    @RequiresApi(19)
-    static class PopupWindowCompatApi19Impl extends PopupWindowCompatBaseImpl {
-        @Override
-        public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
-                int gravity) {
-            popup.showAsDropDown(anchor, xoff, yoff, gravity);
-        }
-    }
-
-    @RequiresApi(21)
-    static class PopupWindowCompatApi21Impl extends PopupWindowCompatApi19Impl {
-        private static final String TAG = "PopupWindowCompatApi21";
-
-        private static Field sOverlapAnchorField;
-
-        static {
-            try {
-                sOverlapAnchorField = PopupWindow.class.getDeclaredField("mOverlapAnchor");
-                sOverlapAnchorField.setAccessible(true);
-            } catch (NoSuchFieldException e) {
-                Log.i(TAG, "Could not fetch mOverlapAnchor field from PopupWindow", e);
-            }
-        }
-
-        @Override
-        public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
-            if (sOverlapAnchorField != null) {
-                try {
-                    sOverlapAnchorField.set(popupWindow, overlapAnchor);
-                } catch (IllegalAccessException e) {
-                    Log.i(TAG, "Could not set overlap anchor field in PopupWindow", e);
-                }
-            }
-        }
-
-        @Override
-        public boolean getOverlapAnchor(PopupWindow popupWindow) {
-            if (sOverlapAnchorField != null) {
-                try {
-                    return (Boolean) sOverlapAnchorField.get(popupWindow);
-                } catch (IllegalAccessException e) {
-                    Log.i(TAG, "Could not get overlap anchor field in PopupWindow", e);
-                }
-            }
-            return false;
-        }
-    }
-
-    @RequiresApi(23)
-    static class PopupWindowCompatApi23Impl extends PopupWindowCompatApi21Impl {
-        @Override
-        public void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
-            popupWindow.setOverlapAnchor(overlapAnchor);
-        }
-
-        @Override
-        public boolean getOverlapAnchor(PopupWindow popupWindow) {
-            return popupWindow.getOverlapAnchor();
-        }
-
-        @Override
-        public void setWindowLayoutType(PopupWindow popupWindow, int layoutType) {
-            popupWindow.setWindowLayoutType(layoutType);
-        }
-
-        @Override
-        public int getWindowLayoutType(PopupWindow popupWindow) {
-            return popupWindow.getWindowLayoutType();
-        }
-    }
-
-    /**
-     * Select the correct implementation to use for the current platform.
-     */
-    static final PopupWindowCompatBaseImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 23) {
-            IMPL = new PopupWindowCompatApi23Impl();
-        } else if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new PopupWindowCompatApi21Impl();
-        } else if (Build.VERSION.SDK_INT >= 19) {
-            IMPL = new PopupWindowCompatApi19Impl();
-        } else {
-            IMPL = new PopupWindowCompatBaseImpl();
-        }
-    }
+    private static Field sOverlapAnchorField;
+    private static boolean sOverlapAnchorFieldAttempted;
 
     private PopupWindowCompat() {
         // This class is not publicly instantiable.
@@ -217,7 +65,19 @@
      */
     public static void showAsDropDown(@NonNull PopupWindow popup, @NonNull View anchor,
             int xoff, int yoff, int gravity) {
-        IMPL.showAsDropDown(popup, anchor, xoff, yoff, gravity);
+        if (Build.VERSION.SDK_INT >= 19) {
+            popup.showAsDropDown(anchor, xoff, yoff, gravity);
+        } else {
+            int xoff1 = xoff;
+            final int hgrav = GravityCompat.getAbsoluteGravity(gravity,
+                    ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK;
+            if (hgrav == Gravity.RIGHT) {
+                // Flip the location to align the right sides of the popup and
+                // anchor instead of left.
+                xoff1 -= (popup.getWidth() - anchor.getWidth());
+            }
+            popup.showAsDropDown(anchor, xoff1, yoff);
+        }
     }
 
     /**
@@ -227,7 +87,26 @@
      * @param overlapAnchor Whether the popup should overlap its anchor.
      */
     public static void setOverlapAnchor(@NonNull PopupWindow popupWindow, boolean overlapAnchor) {
-        IMPL.setOverlapAnchor(popupWindow, overlapAnchor);
+        if (Build.VERSION.SDK_INT >= 23) {
+            popupWindow.setOverlapAnchor(overlapAnchor);
+        } else if (Build.VERSION.SDK_INT >= 21) {
+            if (!sOverlapAnchorFieldAttempted) {
+                try {
+                    sOverlapAnchorField = PopupWindow.class.getDeclaredField("mOverlapAnchor");
+                    sOverlapAnchorField.setAccessible(true);
+                } catch (NoSuchFieldException e) {
+                    Log.i(TAG, "Could not fetch mOverlapAnchor field from PopupWindow", e);
+                }
+                sOverlapAnchorFieldAttempted = true;
+            }
+            if (sOverlapAnchorField != null) {
+                try {
+                    sOverlapAnchorField.set(popupWindow, overlapAnchor);
+                } catch (IllegalAccessException e) {
+                    Log.i(TAG, "Could not set overlap anchor field in PopupWindow", e);
+                }
+            }
+        }
     }
 
     /**
@@ -237,7 +116,28 @@
      * @return Whether the popup should overlap its anchor.
      */
     public static boolean getOverlapAnchor(@NonNull PopupWindow popupWindow) {
-        return IMPL.getOverlapAnchor(popupWindow);
+        if (Build.VERSION.SDK_INT >= 23) {
+            return popupWindow.getOverlapAnchor();
+        }
+        if (Build.VERSION.SDK_INT >= 21) {
+            if (!sOverlapAnchorFieldAttempted) {
+                try {
+                    sOverlapAnchorField = PopupWindow.class.getDeclaredField("mOverlapAnchor");
+                    sOverlapAnchorField.setAccessible(true);
+                } catch (NoSuchFieldException e) {
+                    Log.i(TAG, "Could not fetch mOverlapAnchor field from PopupWindow", e);
+                }
+                sOverlapAnchorFieldAttempted = true;
+            }
+            if (sOverlapAnchorField != null) {
+                try {
+                    return (Boolean) sOverlapAnchorField.get(popupWindow);
+                } catch (IllegalAccessException e) {
+                    Log.i(TAG, "Could not get overlap anchor field in PopupWindow", e);
+                }
+            }
+        }
+        return false;
     }
 
     /**
@@ -250,7 +150,28 @@
      * @see android.view.WindowManager.LayoutParams#type
      */
     public static void setWindowLayoutType(@NonNull PopupWindow popupWindow, int layoutType) {
-        IMPL.setWindowLayoutType(popupWindow, layoutType);
+        if (Build.VERSION.SDK_INT >= 23) {
+            popupWindow.setWindowLayoutType(layoutType);
+            return;
+        }
+
+        if (!sSetWindowLayoutTypeMethodAttempted) {
+            try {
+                sSetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
+                        "setWindowLayoutType", int.class);
+                sSetWindowLayoutTypeMethod.setAccessible(true);
+            } catch (Exception e) {
+                // Reflection method fetch failed. Oh well.
+            }
+            sSetWindowLayoutTypeMethodAttempted = true;
+        }
+        if (sSetWindowLayoutTypeMethod != null) {
+            try {
+                sSetWindowLayoutTypeMethod.invoke(popupWindow, layoutType);
+            } catch (Exception e) {
+                // Reflection call failed. Oh well.
+            }
+        }
     }
 
     /**
@@ -259,6 +180,27 @@
      * @see #setWindowLayoutType(PopupWindow popupWindow, int)
      */
     public static int getWindowLayoutType(@NonNull PopupWindow popupWindow) {
-        return IMPL.getWindowLayoutType(popupWindow);
+        if (Build.VERSION.SDK_INT >= 23) {
+            return popupWindow.getWindowLayoutType();
+        }
+
+        if (!sGetWindowLayoutTypeMethodAttempted) {
+            try {
+                sGetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod(
+                        "getWindowLayoutType");
+                sGetWindowLayoutTypeMethod.setAccessible(true);
+            } catch (Exception e) {
+                // Reflection method fetch failed. Oh well.
+            }
+            sGetWindowLayoutTypeMethodAttempted = true;
+        }
+        if (sGetWindowLayoutTypeMethod != null) {
+            try {
+                return (Integer) sGetWindowLayoutTypeMethod.invoke(popupWindow);
+            } catch (Exception e) {
+                // Reflection call failed. Oh well.
+            }
+        }
+        return 0;
     }
 }
diff --git a/compat/src/main/java/androidx/core/widget/TextViewCompat.java b/compat/src/main/java/androidx/core/widget/TextViewCompat.java
index f82e025..d67d61c 100644
--- a/compat/src/main/java/androidx/core/widget/TextViewCompat.java
+++ b/compat/src/main/java/androidx/core/widget/TextViewCompat.java
@@ -38,7 +38,6 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.StyleRes;
 
@@ -54,6 +53,7 @@
  * Helper for accessing features in {@link TextView}.
  */
 public final class TextViewCompat {
+    private static final String LOG_TAG = "TextViewCompat";
 
     /**
      * The TextView does not auto-size text (default).
@@ -72,486 +72,39 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface AutoSizeTextType {}
 
+    private static Field sMaximumField;
+    private static boolean sMaximumFieldFetched;
+    private static Field sMaxModeField;
+    private static boolean sMaxModeFieldFetched;
+
+    private static Field sMinimumField;
+    private static boolean sMinimumFieldFetched;
+    private static Field sMinModeField;
+    private static boolean sMinModeFieldFetched;
+
+    private static final int LINES = 1;
+
     // Hide constructor
     private TextViewCompat() {}
 
-    static class TextViewCompatBaseImpl {
-        private static final String LOG_TAG = "TextViewCompatBase";
-        private static final int LINES = 1;
-
-        private static Field sMaximumField;
-        private static boolean sMaximumFieldFetched;
-        private static Field sMaxModeField;
-        private static boolean sMaxModeFieldFetched;
-
-        private static Field sMinimumField;
-        private static boolean sMinimumFieldFetched;
-        private static Field sMinModeField;
-        private static boolean sMinModeFieldFetched;
-
-        public void setCompoundDrawablesRelative(@NonNull TextView textView,
-                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
-                @Nullable Drawable bottom) {
-            textView.setCompoundDrawables(start, top, end, bottom);
+    private static Field retrieveField(String fieldName) {
+        Field field = null;
+        try {
+            field = TextView.class.getDeclaredField(fieldName);
+            field.setAccessible(true);
+        } catch (NoSuchFieldException e) {
+            Log.e(LOG_TAG, "Could not retrieve " + fieldName + " field.");
         }
-
-        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
-                @Nullable Drawable bottom) {
-            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
-        }
-
-        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
-                @DrawableRes int bottom) {
-            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
-        }
-
-        private static Field retrieveField(String fieldName) {
-            Field field = null;
-            try {
-                field = TextView.class.getDeclaredField(fieldName);
-                field.setAccessible(true);
-            } catch (NoSuchFieldException e) {
-                Log.e(LOG_TAG, "Could not retrieve " + fieldName + " field.");
-            }
-            return field;
-        }
-
-        private static int retrieveIntFromField(Field field, TextView textView) {
-            try {
-                return field.getInt(textView);
-            } catch (IllegalAccessException e) {
-                Log.d(LOG_TAG, "Could not retrieve value of " + field.getName() + " field.");
-            }
-            return -1;
-        }
-
-        public int getMaxLines(TextView textView) {
-            if (!sMaxModeFieldFetched) {
-                sMaxModeField = retrieveField("mMaxMode");
-                sMaxModeFieldFetched = true;
-            }
-            if (sMaxModeField != null && retrieveIntFromField(sMaxModeField, textView) == LINES) {
-                // If the max mode is using lines, we can grab the maximum value
-                if (!sMaximumFieldFetched) {
-                    sMaximumField = retrieveField("mMaximum");
-                    sMaximumFieldFetched = true;
-                }
-                if (sMaximumField != null) {
-                    return retrieveIntFromField(sMaximumField, textView);
-                }
-            }
-            return -1;
-        }
-
-        public int getMinLines(TextView textView) {
-            if (!sMinModeFieldFetched) {
-                sMinModeField = retrieveField("mMinMode");
-                sMinModeFieldFetched = true;
-            }
-            if (sMinModeField != null && retrieveIntFromField(sMinModeField, textView) == LINES) {
-                // If the min mode is using lines, we can grab the maximum value
-                if (!sMinimumFieldFetched) {
-                    sMinimumField = retrieveField("mMinimum");
-                    sMinimumFieldFetched = true;
-                }
-                if (sMinimumField != null) {
-                    return retrieveIntFromField(sMinimumField, textView);
-                }
-            }
-            return -1;
-        }
-
-        @SuppressWarnings("deprecation")
-        public void setTextAppearance(TextView textView, @StyleRes int resId) {
-            textView.setTextAppearance(textView.getContext(), resId);
-        }
-
-        public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
-            return textView.getCompoundDrawables();
-        }
-
-        public void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) {
-            if (textView instanceof AutoSizeableTextView) {
-                ((AutoSizeableTextView) textView).setAutoSizeTextTypeWithDefaults(autoSizeTextType);
-            }
-        }
-
-        public void setAutoSizeTextTypeUniformWithConfiguration(
-                TextView textView,
-                int autoSizeMinTextSize,
-                int autoSizeMaxTextSize,
-                int autoSizeStepGranularity,
-                int unit) throws IllegalArgumentException {
-            if (textView instanceof AutoSizeableTextView) {
-                ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithConfiguration(
-                        autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
-            }
-        }
-
-        public void setAutoSizeTextTypeUniformWithPresetSizes(TextView textView,
-                @NonNull int[] presetSizes, int unit) throws IllegalArgumentException {
-            if (textView instanceof AutoSizeableTextView) {
-                ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithPresetSizes(
-                        presetSizes, unit);
-            }
-        }
-
-        public int getAutoSizeTextType(TextView textView) {
-            if (textView instanceof AutoSizeableTextView) {
-                return ((AutoSizeableTextView) textView).getAutoSizeTextType();
-            }
-            return AUTO_SIZE_TEXT_TYPE_NONE;
-        }
-
-        public int getAutoSizeStepGranularity(TextView textView) {
-            if (textView instanceof AutoSizeableTextView) {
-                return ((AutoSizeableTextView) textView).getAutoSizeStepGranularity();
-            }
-            return -1;
-        }
-
-        public int getAutoSizeMinTextSize(TextView textView) {
-            if (textView instanceof AutoSizeableTextView) {
-                return ((AutoSizeableTextView) textView).getAutoSizeMinTextSize();
-            }
-            return -1;
-        }
-
-        public int getAutoSizeMaxTextSize(TextView textView) {
-            if (textView instanceof AutoSizeableTextView) {
-                return ((AutoSizeableTextView) textView).getAutoSizeMaxTextSize();
-            }
-            return -1;
-        }
-
-        public int[] getAutoSizeTextAvailableSizes(TextView textView) {
-            if (textView instanceof AutoSizeableTextView) {
-                return ((AutoSizeableTextView) textView).getAutoSizeTextAvailableSizes();
-            }
-            return new int[0];
-        }
-
-        public void setCustomSelectionActionModeCallback(TextView textView,
-                ActionMode.Callback callback) {
-            textView.setCustomSelectionActionModeCallback(callback);
-        }
+        return field;
     }
 
-    @RequiresApi(16)
-    static class TextViewCompatApi16Impl extends TextViewCompatBaseImpl {
-        @Override
-        public int getMaxLines(TextView textView) {
-            return textView.getMaxLines();
+    private static int retrieveIntFromField(Field field, TextView textView) {
+        try {
+            return field.getInt(textView);
+        } catch (IllegalAccessException e) {
+            Log.d(LOG_TAG, "Could not retrieve value of " + field.getName() + " field.");
         }
-
-        @Override
-        public int getMinLines(TextView textView) {
-            return textView.getMinLines();
-        }
-    }
-
-    @RequiresApi(17)
-    static class TextViewCompatApi17Impl extends TextViewCompatApi16Impl {
-        @Override
-        public void setCompoundDrawablesRelative(@NonNull TextView textView,
-                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
-                @Nullable Drawable bottom) {
-            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-            textView.setCompoundDrawables(rtl ? end : start, top, rtl ? start : end, bottom);
-        }
-
-        @Override
-        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
-                @Nullable Drawable bottom) {
-            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
-                    rtl ? start : end,  bottom);
-        }
-
-        @Override
-        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
-                @DrawableRes int bottom) {
-            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
-                    rtl ? start : end, bottom);
-        }
-
-        @Override
-        public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
-            final boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-            final Drawable[] compounds = textView.getCompoundDrawables();
-            if (rtl) {
-                // If we're on RTL, we need to invert the horizontal result like above
-                final Drawable start = compounds[2];
-                final Drawable end = compounds[0];
-                compounds[0] = start;
-                compounds[2] = end;
-            }
-            return compounds;
-        }
-    }
-
-    @RequiresApi(18)
-    static class TextViewCompatApi18Impl extends TextViewCompatApi17Impl {
-        @Override
-        public void setCompoundDrawablesRelative(@NonNull TextView textView,
-                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
-                @Nullable Drawable bottom) {
-            textView.setCompoundDrawablesRelative(start, top, end, bottom);
-        }
-
-        @Override
-        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
-                @Nullable Drawable bottom) {
-            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
-        }
-
-        @Override
-        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
-                @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
-                @DrawableRes int bottom) {
-            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
-        }
-
-        @Override
-        public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
-            return textView.getCompoundDrawablesRelative();
-        }
-    }
-
-    @RequiresApi(23)
-    static class TextViewCompatApi23Impl extends TextViewCompatApi18Impl {
-        @Override
-        public void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
-            textView.setTextAppearance(resId);
-        }
-    }
-
-    @RequiresApi(26)
-    static class TextViewCompatApi26Impl extends TextViewCompatApi23Impl {
-        @Override
-        public void setCustomSelectionActionModeCallback(final TextView textView,
-                final ActionMode.Callback callback) {
-            if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O
-                    && Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1) {
-                super.setCustomSelectionActionModeCallback(textView, callback);
-                return;
-            }
-
-
-            // A bug in O and O_MR1 causes a number of options for handling the ACTION_PROCESS_TEXT
-            // intent after selection to not be displayed in the menu, although they should be.
-            // Here we fix this, by removing the menu items created by the framework code, and
-            // adding them (and the missing ones) back correctly.
-            textView.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
-                // This constant should be correlated with its definition in the
-                // android.widget.Editor class.
-                private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
-
-                // References to the MenuBuilder class and its removeItemAt(int) method.
-                // Since in most cases the menu instance processed by this callback is going
-                // to be a MenuBuilder, we keep these references to avoid querying for them
-                // frequently by reflection in recomputeProcessTextMenuItems.
-                private Class mMenuBuilderClass;
-                private Method mMenuBuilderRemoveItemAtMethod;
-                private boolean mCanUseMenuBuilderReferences;
-                private boolean mInitializedMenuBuilderReferences = false;
-
-                @Override
-                public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-                    return callback.onCreateActionMode(mode, menu);
-                }
-
-                @Override
-                public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-                    recomputeProcessTextMenuItems(menu);
-                    return callback.onPrepareActionMode(mode, menu);
-                }
-
-                @Override
-                public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-                    return callback.onActionItemClicked(mode, item);
-                }
-
-                @Override
-                public void onDestroyActionMode(ActionMode mode) {
-                    callback.onDestroyActionMode(mode);
-                }
-
-                private void recomputeProcessTextMenuItems(final Menu menu) {
-                    final Context context = textView.getContext();
-                    final PackageManager packageManager = context.getPackageManager();
-
-                    if (!mInitializedMenuBuilderReferences) {
-                        mInitializedMenuBuilderReferences = true;
-                        try {
-                            mMenuBuilderClass =
-                                    Class.forName("com.android.internal.view.menu.MenuBuilder");
-                            mMenuBuilderRemoveItemAtMethod = mMenuBuilderClass
-                                    .getDeclaredMethod("removeItemAt", Integer.TYPE);
-                            mCanUseMenuBuilderReferences = true;
-                        } catch (ClassNotFoundException | NoSuchMethodException e) {
-                            mMenuBuilderClass = null;
-                            mMenuBuilderRemoveItemAtMethod = null;
-                            mCanUseMenuBuilderReferences = false;
-                        }
-                    }
-                    // Remove the menu items created for ACTION_PROCESS_TEXT handlers.
-                    try {
-                        final Method removeItemAtMethod =
-                                (mCanUseMenuBuilderReferences && mMenuBuilderClass.isInstance(menu))
-                                        ? mMenuBuilderRemoveItemAtMethod
-                                        : menu.getClass()
-                                                .getDeclaredMethod("removeItemAt", Integer.TYPE);
-                        for (int i = menu.size() - 1; i >= 0; --i) {
-                            final MenuItem item = menu.getItem(i);
-                            if (item.getIntent() != null && Intent.ACTION_PROCESS_TEXT
-                                    .equals(item.getIntent().getAction())) {
-                                removeItemAtMethod.invoke(menu, i);
-                            }
-                        }
-                    } catch (NoSuchMethodException | IllegalAccessException
-                            | InvocationTargetException e) {
-                        // There is a menu custom implementation used which is not providing
-                        // a removeItemAt(int) menu. There is nothing we can do in this case.
-                        return;
-                    }
-
-                    // Populate the menu again with the ACTION_PROCESS_TEXT handlers.
-                    final List<ResolveInfo> supportedActivities =
-                            getSupportedActivities(context, packageManager);
-                    for (int i = 0; i < supportedActivities.size(); ++i) {
-                        final ResolveInfo info = supportedActivities.get(i);
-                        menu.add(Menu.NONE, Menu.NONE,
-                                MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
-                                info.loadLabel(packageManager))
-                                .setIntent(createProcessTextIntentForResolveInfo(info, textView))
-                                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-                    }
-                }
-
-                private List<ResolveInfo> getSupportedActivities(final Context context,
-                        final PackageManager packageManager) {
-                    final List<ResolveInfo> supportedActivities = new ArrayList<>();
-                    boolean canStartActivityForResult = context instanceof Activity;
-                    if (!canStartActivityForResult) {
-                        return supportedActivities;
-                    }
-                    final List<ResolveInfo> unfiltered =
-                            packageManager.queryIntentActivities(createProcessTextIntent(), 0);
-                    for (ResolveInfo info : unfiltered) {
-                        if (isSupportedActivity(info, context)) {
-                            supportedActivities.add(info);
-                        }
-                    }
-                    return supportedActivities;
-                }
-
-                private boolean isSupportedActivity(final ResolveInfo info, final Context context) {
-                    if (context.getPackageName().equals(info.activityInfo.packageName)) {
-                        return true;
-                    }
-                    if (!info.activityInfo.exported) {
-                        return false;
-                    }
-                    return info.activityInfo.permission == null
-                            || context.checkSelfPermission(info.activityInfo.permission)
-                                == PackageManager.PERMISSION_GRANTED;
-                }
-
-                private Intent createProcessTextIntentForResolveInfo(final ResolveInfo info,
-                        final TextView textView) {
-                    return createProcessTextIntent()
-                            .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, !isEditable(textView))
-                            .setClassName(info.activityInfo.packageName, info.activityInfo.name);
-                }
-
-                private boolean isEditable(final TextView textView) {
-                    return textView instanceof Editable
-                            && textView.onCheckIsTextEditor()
-                            && textView.isEnabled();
-                }
-
-                private Intent createProcessTextIntent() {
-                    return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain");
-                }
-            });
-        }
-    }
-
-    @RequiresApi(27)
-    static class TextViewCompatApi27Impl extends TextViewCompatApi26Impl {
-        @Override
-        public void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) {
-            textView.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
-        }
-
-        @Override
-        public void setAutoSizeTextTypeUniformWithConfiguration(
-                TextView textView,
-                int autoSizeMinTextSize,
-                int autoSizeMaxTextSize,
-                int autoSizeStepGranularity,
-                int unit) throws IllegalArgumentException {
-            textView.setAutoSizeTextTypeUniformWithConfiguration(
-                    autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
-        }
-
-        @Override
-        public void setAutoSizeTextTypeUniformWithPresetSizes(TextView textView,
-                @NonNull int[] presetSizes, int unit) throws IllegalArgumentException {
-            textView.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit);
-        }
-
-        @Override
-        public int getAutoSizeTextType(TextView textView) {
-            return textView.getAutoSizeTextType();
-        }
-
-        @Override
-        public int getAutoSizeStepGranularity(TextView textView) {
-            return textView.getAutoSizeStepGranularity();
-        }
-
-        @Override
-        public int getAutoSizeMinTextSize(TextView textView) {
-            return textView.getAutoSizeMinTextSize();
-        }
-
-        @Override
-        public int getAutoSizeMaxTextSize(TextView textView) {
-            return textView.getAutoSizeMaxTextSize();
-        }
-
-        @Override
-        public int[] getAutoSizeTextAvailableSizes(TextView textView) {
-            return textView.getAutoSizeTextAvailableSizes();
-        }
-    }
-
-    static final TextViewCompatBaseImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 27) {
-            IMPL = new TextViewCompatApi27Impl();
-        } else if (Build.VERSION.SDK_INT >= 26) {
-            IMPL = new TextViewCompatApi26Impl();
-        } else if (Build.VERSION.SDK_INT >= 23) {
-            IMPL = new TextViewCompatApi23Impl();
-        } else if (Build.VERSION.SDK_INT >= 18) {
-            IMPL = new TextViewCompatApi18Impl();
-        } else if (Build.VERSION.SDK_INT >= 17) {
-            IMPL = new TextViewCompatApi17Impl();
-        } else if (Build.VERSION.SDK_INT >= 16) {
-            IMPL = new TextViewCompatApi16Impl();
-        } else {
-            IMPL = new TextViewCompatBaseImpl();
-        }
+        return -1;
     }
 
     /**
@@ -572,7 +125,14 @@
     public static void setCompoundDrawablesRelative(@NonNull TextView textView,
             @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
             @Nullable Drawable bottom) {
-        IMPL.setCompoundDrawablesRelative(textView, start, top, end, bottom);
+        if (Build.VERSION.SDK_INT >= 18) {
+            textView.setCompoundDrawablesRelative(start, top, end, bottom);
+        } else if (Build.VERSION.SDK_INT >= 17) {
+            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+            textView.setCompoundDrawables(rtl ? end : start, top, rtl ? start : end, bottom);
+        } else {
+            textView.setCompoundDrawables(start, top, end, bottom);
+        }
     }
 
     /**
@@ -592,7 +152,15 @@
     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
             @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
             @Nullable Drawable bottom) {
-        IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom);
+        if (Build.VERSION.SDK_INT >= 18) {
+            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
+        } else if (Build.VERSION.SDK_INT >= 17) {
+            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
+                    rtl ? start : end,  bottom);
+        } else {
+            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+        }
     }
 
     /**
@@ -616,7 +184,15 @@
     public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
             @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
             @DrawableRes int bottom) {
-        IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom);
+        if (Build.VERSION.SDK_INT >= 18) {
+            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
+        } else if (Build.VERSION.SDK_INT >= 17) {
+            boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+            textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top,
+                    rtl ? start : end, bottom);
+        } else {
+            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+        }
     }
 
     /**
@@ -624,7 +200,25 @@
      * height was set in pixels instead.
      */
     public static int getMaxLines(@NonNull TextView textView) {
-        return IMPL.getMaxLines(textView);
+        if (Build.VERSION.SDK_INT >= 16) {
+            return textView.getMaxLines();
+        }
+
+        if (!sMaxModeFieldFetched) {
+            sMaxModeField = retrieveField("mMaxMode");
+            sMaxModeFieldFetched = true;
+        }
+        if (sMaxModeField != null && retrieveIntFromField(sMaxModeField, textView) == LINES) {
+            // If the max mode is using lines, we can grab the maximum value
+            if (!sMaximumFieldFetched) {
+                sMaximumField = retrieveField("mMaximum");
+                sMaximumFieldFetched = true;
+            }
+            if (sMaximumField != null) {
+                return retrieveIntFromField(sMaximumField, textView);
+            }
+        }
+        return -1;
     }
 
     /**
@@ -632,7 +226,25 @@
      * height was set in pixels instead.
      */
     public static int getMinLines(@NonNull TextView textView) {
-        return IMPL.getMinLines(textView);
+        if (Build.VERSION.SDK_INT >= 16) {
+            return textView.getMinLines();
+        }
+
+        if (!sMinModeFieldFetched) {
+            sMinModeField = retrieveField("mMinMode");
+            sMinModeFieldFetched = true;
+        }
+        if (sMinModeField != null && retrieveIntFromField(sMinModeField, textView) == LINES) {
+            // If the min mode is using lines, we can grab the maximum value
+            if (!sMinimumFieldFetched) {
+                sMinimumField = retrieveField("mMinimum");
+                sMinimumFieldFetched = true;
+            }
+            if (sMinimumField != null) {
+                return retrieveIntFromField(sMinimumField, textView);
+            }
+        }
+        return -1;
     }
 
     /**
@@ -645,7 +257,11 @@
      * @param resId    The resource identifier of the style to apply.
      */
     public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
-        IMPL.setTextAppearance(textView, resId);
+        if (Build.VERSION.SDK_INT >= 23) {
+            textView.setTextAppearance(resId);
+        } else {
+            textView.setTextAppearance(textView.getContext(), resId);
+        }
     }
 
     /**
@@ -653,7 +269,22 @@
      */
     @NonNull
     public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
-        return IMPL.getCompoundDrawablesRelative(textView);
+        if (Build.VERSION.SDK_INT >= 18) {
+            return textView.getCompoundDrawablesRelative();
+        }
+        if (Build.VERSION.SDK_INT >= 17) {
+            final boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+            final Drawable[] compounds = textView.getCompoundDrawables();
+            if (rtl) {
+                // If we're on RTL, we need to invert the horizontal result like above
+                final Drawable start = compounds[2];
+                final Drawable end = compounds[0];
+                compounds[0] = start;
+                compounds[2] = end;
+            }
+            return compounds;
+        }
+        return textView.getCompoundDrawables();
     }
 
     /**
@@ -666,9 +297,14 @@
      *
      * @attr name android:autoSizeTextType
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static void setAutoSizeTextTypeWithDefaults(@NonNull TextView textView,
             int autoSizeTextType) {
-        IMPL.setAutoSizeTextTypeWithDefaults(textView, autoSizeTextType);
+        if (Build.VERSION.SDK_INT >= 27) {
+            textView.setAutoSizeTextTypeWithDefaults(autoSizeTextType);
+        } else if (textView instanceof AutoSizeableTextView) {
+            ((AutoSizeableTextView) textView).setAutoSizeTextTypeWithDefaults(autoSizeTextType);
+        }
     }
 
     /**
@@ -692,14 +328,20 @@
      * @attr name android:autoSizeMaxTextSize
      * @attr name android:autoSizeStepGranularity
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static void setAutoSizeTextTypeUniformWithConfiguration(
             @NonNull TextView textView,
             int autoSizeMinTextSize,
             int autoSizeMaxTextSize,
             int autoSizeStepGranularity,
             int unit) throws IllegalArgumentException {
-        IMPL.setAutoSizeTextTypeUniformWithConfiguration(textView, autoSizeMinTextSize,
-                autoSizeMaxTextSize, autoSizeStepGranularity, unit);
+        if (Build.VERSION.SDK_INT >= 27) {
+            textView.setAutoSizeTextTypeUniformWithConfiguration(
+                    autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
+        } else if (textView instanceof AutoSizeableTextView) {
+            ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithConfiguration(
+                    autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit);
+        }
     }
 
     /**
@@ -716,9 +358,15 @@
      * @attr name android:autoSizeTextType
      * @attr name android:autoSizePresetSizes
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull TextView textView,
             @NonNull int[] presetSizes, int unit) throws IllegalArgumentException {
-        IMPL.setAutoSizeTextTypeUniformWithPresetSizes(textView, presetSizes, unit);
+        if (Build.VERSION.SDK_INT >= 27) {
+            textView.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit);
+        } else if (textView instanceof AutoSizeableTextView) {
+            ((AutoSizeableTextView) textView).setAutoSizeTextTypeUniformWithPresetSizes(
+                    presetSizes, unit);
+        }
     }
 
     /**
@@ -730,8 +378,15 @@
      *
      * @attr name android:autoSizeTextType
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static int getAutoSizeTextType(@NonNull TextView textView) {
-        return IMPL.getAutoSizeTextType(textView);
+        if (Build.VERSION.SDK_INT >= 27) {
+            return textView.getAutoSizeTextType();
+        }
+        if (textView instanceof AutoSizeableTextView) {
+            return ((AutoSizeableTextView) textView).getAutoSizeTextType();
+        }
+        return AUTO_SIZE_TEXT_TYPE_NONE;
     }
 
     /**
@@ -739,8 +394,15 @@
      *
      * @attr name android:autoSizeStepGranularity
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static int getAutoSizeStepGranularity(@NonNull TextView textView) {
-        return IMPL.getAutoSizeStepGranularity(textView);
+        if (Build.VERSION.SDK_INT >= 27) {
+            return textView.getAutoSizeStepGranularity();
+        }
+        if (textView instanceof AutoSizeableTextView) {
+            return ((AutoSizeableTextView) textView).getAutoSizeStepGranularity();
+        }
+        return -1;
     }
 
     /**
@@ -749,8 +411,15 @@
      *
      * @attr name android:autoSizeMinTextSize
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static int getAutoSizeMinTextSize(@NonNull TextView textView) {
-        return IMPL.getAutoSizeMinTextSize(textView);
+        if (Build.VERSION.SDK_INT >= 27) {
+            return textView.getAutoSizeMinTextSize();
+        }
+        if (textView instanceof AutoSizeableTextView) {
+            return ((AutoSizeableTextView) textView).getAutoSizeMinTextSize();
+        }
+        return -1;
     }
 
     /**
@@ -759,8 +428,15 @@
      *
      * @attr name android:autoSizeMaxTextSize
      */
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static int getAutoSizeMaxTextSize(@NonNull TextView textView) {
-        return IMPL.getAutoSizeMaxTextSize(textView);
+        if (Build.VERSION.SDK_INT >= 27) {
+            return textView.getAutoSizeMaxTextSize();
+        }
+        if (textView instanceof AutoSizeableTextView) {
+            return ((AutoSizeableTextView) textView).getAutoSizeMaxTextSize();
+        }
+        return -1;
     }
 
     /**
@@ -769,8 +445,15 @@
      * @attr name android:autoSizePresetSizes
      */
     @NonNull
+    @SuppressWarnings("RedundantCast") // Intentionally invoking interface method.
     public static int[] getAutoSizeTextAvailableSizes(@NonNull TextView textView) {
-        return IMPL.getAutoSizeTextAvailableSizes(textView);
+        if (Build.VERSION.SDK_INT >= 27) {
+            return textView.getAutoSizeTextAvailableSizes();
+        }
+        if (textView instanceof AutoSizeableTextView) {
+            return ((AutoSizeableTextView) textView).getAutoSizeTextAvailableSizes();
+        }
+        return new int[0];
     }
 
     /**
@@ -795,8 +478,149 @@
      * @param textView The TextView to set the action selection mode callback on.
      * @param callback The action selection mode callback to set on textView.
      */
-    public static void setCustomSelectionActionModeCallback(@NonNull TextView textView,
-                @NonNull ActionMode.Callback callback) {
-        IMPL.setCustomSelectionActionModeCallback(textView, callback);
+    public static void setCustomSelectionActionModeCallback(@NonNull final TextView textView,
+                @NonNull final ActionMode.Callback callback) {
+        if (Build.VERSION.SDK_INT < 26 || Build.VERSION.SDK_INT > 27) {
+            textView.setCustomSelectionActionModeCallback(callback);
+            return;
+        }
+
+        // A bug in O and O_MR1 causes a number of options for handling the ACTION_PROCESS_TEXT
+        // intent after selection to not be displayed in the menu, although they should be.
+        // Here we fix this, by removing the menu items created by the framework code, and
+        // adding them (and the missing ones) back correctly.
+        textView.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
+            // This constant should be correlated with its definition in the
+            // android.widget.Editor class.
+            private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+
+            // References to the MenuBuilder class and its removeItemAt(int) method.
+            // Since in most cases the menu instance processed by this callback is going
+            // to be a MenuBuilder, we keep these references to avoid querying for them
+            // frequently by reflection in recomputeProcessTextMenuItems.
+            private Class mMenuBuilderClass;
+            private Method mMenuBuilderRemoveItemAtMethod;
+            private boolean mCanUseMenuBuilderReferences;
+            private boolean mInitializedMenuBuilderReferences = false;
+
+            @Override
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                return callback.onCreateActionMode(mode, menu);
+            }
+
+            @Override
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                recomputeProcessTextMenuItems(menu);
+                return callback.onPrepareActionMode(mode, menu);
+            }
+
+            @Override
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                return callback.onActionItemClicked(mode, item);
+            }
+
+            @Override
+            public void onDestroyActionMode(ActionMode mode) {
+                callback.onDestroyActionMode(mode);
+            }
+
+            private void recomputeProcessTextMenuItems(final Menu menu) {
+                final Context context = textView.getContext();
+                final PackageManager packageManager = context.getPackageManager();
+
+                if (!mInitializedMenuBuilderReferences) {
+                    mInitializedMenuBuilderReferences = true;
+                    try {
+                        mMenuBuilderClass =
+                                Class.forName("com.android.internal.view.menu.MenuBuilder");
+                        mMenuBuilderRemoveItemAtMethod = mMenuBuilderClass
+                                .getDeclaredMethod("removeItemAt", Integer.TYPE);
+                        mCanUseMenuBuilderReferences = true;
+                    } catch (ClassNotFoundException | NoSuchMethodException e) {
+                        mMenuBuilderClass = null;
+                        mMenuBuilderRemoveItemAtMethod = null;
+                        mCanUseMenuBuilderReferences = false;
+                    }
+                }
+                // Remove the menu items created for ACTION_PROCESS_TEXT handlers.
+                try {
+                    final Method removeItemAtMethod =
+                            (mCanUseMenuBuilderReferences && mMenuBuilderClass.isInstance(menu))
+                                    ? mMenuBuilderRemoveItemAtMethod
+                                    : menu.getClass()
+                                            .getDeclaredMethod("removeItemAt", Integer.TYPE);
+                    for (int i = menu.size() - 1; i >= 0; --i) {
+                        final MenuItem item = menu.getItem(i);
+                        if (item.getIntent() != null && Intent.ACTION_PROCESS_TEXT
+                                .equals(item.getIntent().getAction())) {
+                            removeItemAtMethod.invoke(menu, i);
+                        }
+                    }
+                } catch (NoSuchMethodException | IllegalAccessException
+                        | InvocationTargetException e) {
+                    // There is a menu custom implementation used which is not providing
+                    // a removeItemAt(int) menu. There is nothing we can do in this case.
+                    return;
+                }
+
+                // Populate the menu again with the ACTION_PROCESS_TEXT handlers.
+                final List<ResolveInfo> supportedActivities =
+                        getSupportedActivities(context, packageManager);
+                for (int i = 0; i < supportedActivities.size(); ++i) {
+                    final ResolveInfo info = supportedActivities.get(i);
+                    menu.add(Menu.NONE, Menu.NONE,
+                            MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
+                            info.loadLabel(packageManager))
+                            .setIntent(createProcessTextIntentForResolveInfo(info, textView))
+                            .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                }
+            }
+
+            private List<ResolveInfo> getSupportedActivities(final Context context,
+                    final PackageManager packageManager) {
+                final List<ResolveInfo> supportedActivities = new ArrayList<>();
+                boolean canStartActivityForResult = context instanceof Activity;
+                if (!canStartActivityForResult) {
+                    return supportedActivities;
+                }
+                final List<ResolveInfo> unfiltered =
+                        packageManager.queryIntentActivities(createProcessTextIntent(), 0);
+                for (ResolveInfo info : unfiltered) {
+                    if (isSupportedActivity(info, context)) {
+                        supportedActivities.add(info);
+                    }
+                }
+                return supportedActivities;
+            }
+
+            private boolean isSupportedActivity(final ResolveInfo info, final Context context) {
+                if (context.getPackageName().equals(info.activityInfo.packageName)) {
+                    return true;
+                }
+                if (!info.activityInfo.exported) {
+                    return false;
+                }
+                return info.activityInfo.permission == null
+                        || context.checkSelfPermission(info.activityInfo.permission)
+                            == PackageManager.PERMISSION_GRANTED;
+            }
+
+            private Intent createProcessTextIntentForResolveInfo(final ResolveInfo info,
+                    final TextView textView11) {
+                return createProcessTextIntent()
+                        .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, !isEditable(textView11))
+                        .setClassName(info.activityInfo.packageName, info.activityInfo.name);
+            }
+
+            private boolean isEditable(final TextView textView11) {
+                return textView11 instanceof Editable
+                        && textView11.onCheckIsTextEditor()
+                        && textView11.isEnabled();
+            }
+
+            private Intent createProcessTextIntent() {
+                return new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain");
+            }
+        });
     }
 }
diff --git a/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java b/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java
index f32b67a..6776257 100644
--- a/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java
+++ b/documentfile/src/main/java/androidx/documentfile/provider/DocumentFile.java
@@ -140,7 +140,7 @@
      */
     public static boolean isDocumentUri(Context context, Uri uri) {
         if (Build.VERSION.SDK_INT >= 19) {
-            return DocumentsContractApi19.isDocumentUri(context, uri);
+            return DocumentsContract.isDocumentUri(context, uri);
         } else {
             return false;
         }
diff --git a/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java b/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java
index aefbe70..750958d 100644
--- a/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java
+++ b/documentfile/src/main/java/androidx/documentfile/provider/DocumentsContractApi19.java
@@ -35,12 +35,8 @@
     // DocumentsContract API level 24.
     private static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
 
-    public static boolean isDocumentUri(Context context, Uri self) {
-        return DocumentsContract.isDocumentUri(context, self);
-    }
-
     public static boolean isVirtual(Context context, Uri self) {
-        if (!isDocumentUri(context, self)) {
+        if (!DocumentsContract.isDocumentUri(context, self)) {
             return false;
         }
 
diff --git a/fragment/api/current.txt b/fragment/api/current.txt
index 95ed05f..1ee47e3 100644
--- a/fragment/api/current.txt
+++ b/fragment/api/current.txt
@@ -23,7 +23,7 @@
     field public static final int STYLE_NO_TITLE = 1; // 0x1
   }
 
-  public class Fragment implements android.content.ComponentCallbacks android.arch.lifecycle.LifecycleOwner android.view.View.OnCreateContextMenuListener android.arch.lifecycle.ViewModelStoreOwner {
+  public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
     ctor public Fragment();
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public final boolean equals(java.lang.Object);
@@ -39,7 +39,7 @@
     method public final java.lang.Object getHost();
     method public final int getId();
     method public final android.view.LayoutInflater getLayoutInflater();
-    method public android.arch.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
     method public deprecated androidx.loader.app.LoaderManager getLoaderManager();
     method public final androidx.fragment.app.Fragment getParentFragment();
     method public java.lang.Object getReenterTransition();
@@ -56,7 +56,7 @@
     method public final java.lang.CharSequence getText(int);
     method public boolean getUserVisibleHint();
     method public android.view.View getView();
-    method public android.arch.lifecycle.ViewModelStore getViewModelStore();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
     method public final int hashCode();
     method public static androidx.fragment.app.Fragment instantiate(android.content.Context, java.lang.String);
     method public static androidx.fragment.app.Fragment instantiate(android.content.Context, java.lang.String, android.os.Bundle);
@@ -147,12 +147,12 @@
     field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState> CREATOR;
   }
 
-  public class FragmentActivity extends androidx.core.app.SupportActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator android.arch.lifecycle.ViewModelStoreOwner {
+  public class FragmentActivity extends androidx.core.app.SupportActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator androidx.lifecycle.ViewModelStoreOwner {
     ctor public FragmentActivity();
     method public java.lang.Object getLastCustomNonConfigurationInstance();
     method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
     method public deprecated androidx.loader.app.LoaderManager getSupportLoaderManager();
-    method public android.arch.lifecycle.ViewModelStore getViewModelStore();
+    method public androidx.lifecycle.ViewModelStore getViewModelStore();
     method public void onAttachFragment(androidx.fragment.app.Fragment);
     method public void onMultiWindowModeChanged(boolean);
     method public void onPictureInPictureModeChanged(boolean);
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.java
index b318e7b..9c3c7dc 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.java
@@ -18,9 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
 import android.os.Bundle;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
@@ -28,6 +25,9 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import androidx.fragment.app.test.EmptyFragmentTestActivity;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 import org.junit.Assert;
 import org.junit.Rule;
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.java b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.java
index 3f5cfd2..41cc738 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.java
@@ -16,8 +16,8 @@
 
 package androidx.fragment.app;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -25,9 +25,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import android.app.Instrumentation;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
-import android.arch.lifecycle.ViewModelProvider;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
@@ -37,6 +34,9 @@
 import androidx.fragment.app.test.TestViewModel;
 import androidx.fragment.app.test.ViewModelActivity;
 import androidx.fragment.app.test.ViewModelActivity.ViewModelFragment;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.ViewModelProvider;
 
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.java b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.java
index 4e36716..df7f047 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.java
@@ -20,7 +20,6 @@
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import android.arch.lifecycle.ViewModelProvider;
 import android.os.Bundle;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
@@ -30,6 +29,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.test.EmptyFragmentTestActivity;
 import androidx.fragment.app.test.TestViewModel;
+import androidx.lifecycle.ViewModelProvider;
 
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.java b/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.java
index f509382..81433c7 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.java
@@ -16,7 +16,7 @@
 
 package androidx.fragment.app.test;
 
-import android.arch.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModel;
 
 public class TestViewModel extends ViewModel {
     public boolean mCleared = false;
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.java
index 3dca0cc..e13ed7d 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/ViewModelActivity.java
@@ -16,13 +16,13 @@
 
 package androidx.fragment.app.test;
 
-import android.arch.lifecycle.ViewModelProvider;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.test.R;
+import androidx.lifecycle.ViewModelProvider;
 
 public class ViewModelActivity extends FragmentActivity {
     public static final String KEY_FRAGMENT_MODEL = "fragment-model";
diff --git a/fragment/src/main/java/androidx/fragment/app/BaseFragmentActivityApi14.java b/fragment/src/main/java/androidx/fragment/app/BaseFragmentActivityApi14.java
deleted file mode 100644
index d069749..0000000
--- a/fragment/src/main/java/androidx/fragment/app/BaseFragmentActivityApi14.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2018 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 androidx.fragment.app;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.core.app.SupportActivity;
-
-@RequiresApi(14)
-abstract class BaseFragmentActivityApi14 extends SupportActivity {
-
-    // We need to keep track of whether startIntentSenderForResult originated from a Fragment, so we
-    // can conditionally check whether the requestCode collides with our reserved ID space for the
-    // request index (see above). Unfortunately we can't just call
-    // super.startIntentSenderForResult(...) to bypass the check when the call didn't come from a
-    // fragment, since we need to use the ActivityCompat version for backward compatibility.
-    boolean mStartedIntentSenderFromFragment;
-
-    @Override
-    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-        final View v = dispatchFragmentsOnCreateView(parent, name, context, attrs);
-        if (v == null) {
-            return super.onCreateView(parent, name, context, attrs);
-        }
-        return v;
-    }
-
-    @Override
-    public View onCreateView(String name, Context context, AttributeSet attrs) {
-        final View v = dispatchFragmentsOnCreateView(null, name, context, attrs);
-        if (v == null) {
-            return super.onCreateView(name, context, attrs);
-        }
-        return v;
-    }
-
-    abstract View dispatchFragmentsOnCreateView(View parent, String name,
-            Context context, AttributeSet attrs);
-
-    @Override
-    public void startIntentSenderForResult(IntentSender intent, int requestCode,
-            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
-            throws IntentSender.SendIntentException {
-        // If this was started from a Fragment we've already checked the upper 16 bits were not in
-        // use, and then repurposed them for the Fragment's index.
-        if (!mStartedIntentSenderFromFragment) {
-            if (requestCode != -1) {
-                checkForValidRequestCode(requestCode);
-            }
-        }
-        super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
-                extraFlags);
-    }
-
-    /**
-     * Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
-     * an {@link IllegalArgumentException} if the code is not valid.
-     */
-    static void checkForValidRequestCode(int requestCode) {
-        if ((requestCode & 0xffff0000) != 0) {
-            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
-        }
-    }
-}
diff --git a/fragment/src/main/java/androidx/fragment/app/BaseFragmentActivityApi16.java b/fragment/src/main/java/androidx/fragment/app/BaseFragmentActivityApi16.java
deleted file mode 100644
index 07703a4..0000000
--- a/fragment/src/main/java/androidx/fragment/app/BaseFragmentActivityApi16.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2018 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 androidx.fragment.app;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-
-/**
- * Base class for {@code FragmentActivity} to be able to use v16 APIs.
- *
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-abstract class BaseFragmentActivityApi16 extends BaseFragmentActivityApi14 {
-
-    // We need to keep track of whether startActivityForResult originated from a Fragment, so we
-    // can conditionally check whether the requestCode collides with our reserved ID space for the
-    // request index (see above). Unfortunately we can't just call
-    // super.startActivityForResult(...) to bypass the check when the call didn't come from a
-    // fragment, since we need to use the ActivityCompat version for backward compatibility.
-    boolean mStartedActivityFromFragment;
-
-    @RequiresApi(16)
-    @Override
-    public void startActivityForResult(Intent intent, int requestCode,
-            @Nullable Bundle options) {
-        // If this was started from a Fragment we've already checked the upper 16 bits were not in
-        // use, and then repurposed them for the Fragment's index.
-        if (!mStartedActivityFromFragment) {
-            if (requestCode != -1) {
-                checkForValidRequestCode(requestCode);
-            }
-        }
-        super.startActivityForResult(intent, requestCode, options);
-    }
-
-    @RequiresApi(16)
-    @Override
-    public void startIntentSenderForResult(IntentSender intent, int requestCode,
-            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
-            Bundle options) throws IntentSender.SendIntentException {
-        // If this was started from a Fragment we've already checked the upper 16 bits were not in
-        // use, and then repurposed them for the Fragment's index.
-        if (!mStartedIntentSenderFromFragment) {
-            if (requestCode != -1) {
-                checkForValidRequestCode(requestCode);
-            }
-        }
-        super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
-                extraFlags, options);
-    }
-}
diff --git a/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/src/main/java/androidx/fragment/app/Fragment.java
index cd186aa..90728c1 100644
--- a/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -20,11 +20,6 @@
 
 import android.animation.Animator;
 import android.app.Activity;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.ViewModelStore;
-import android.arch.lifecycle.ViewModelStoreOwner;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.Intent;
@@ -58,23 +53,17 @@
 import androidx.core.app.SharedElementCallback;
 import androidx.core.util.DebugUtils;
 import androidx.core.view.LayoutInflaterCompat;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
+import androidx.lifecycle.ViewModelStoreOwner;
 import androidx.loader.app.LoaderManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.reflect.InvocationTargetException;
 
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StringRes;
-import androidx.collection.SimpleArrayMap;
-import androidx.core.app.SharedElementCallback;
-import androidx.core.util.DebugUtils;
-import androidx.core.view.LayoutInflaterCompat;
-import androidx.loader.app.LoaderManager;
-
 /**
  * Static library support version of the framework's {@link android.app.Fragment}.
  * Used to write apps that run on platforms prior to Android 3.0.  When running
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
index e4abec4..1108775 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
@@ -19,10 +19,6 @@
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.app.Activity;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.ViewModelStore;
-import android.arch.lifecycle.ViewModelStoreOwner;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -47,22 +43,17 @@
 import androidx.collection.SparseArrayCompat;
 import androidx.core.app.ActivityCompat;
 import androidx.core.app.SharedElementCallback;
+import androidx.core.app.SupportActivity;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.ViewModelStore;
+import androidx.lifecycle.ViewModelStoreOwner;
 import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
 
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.collection.SparseArrayCompat;
-import androidx.core.app.ActivityCompat;
-import androidx.core.app.SharedElementCallback;
-import androidx.loader.app.LoaderManager;
-
 /**
  * Base class for activities that want to use the support-based
  * {@link Fragment Fragments}.
@@ -74,7 +65,7 @@
  * specify an ID (or tag) in the <code>&lt;fragment></code>.</p>
  * </ul>
  */
-public class FragmentActivity extends BaseFragmentActivityApi16 implements
+public class FragmentActivity extends SupportActivity implements
         ViewModelStoreOwner,
         ActivityCompat.OnRequestPermissionsResultCallback,
         ActivityCompat.RequestPermissionsRequestCodeValidator {
@@ -120,6 +111,19 @@
 
     boolean mRequestedPermissionsFromFragment;
 
+    // We need to keep track of whether startIntentSenderForResult originated from a Fragment, so we
+    // can conditionally check whether the requestCode collides with our reserved ID space for the
+    // request index (see above). Unfortunately we can't just call
+    // super.startIntentSenderForResult(...) to bypass the check when the call didn't come from a
+    // fragment, since we need to use the ActivityCompat version for backward compatibility.
+    boolean mStartedIntentSenderFromFragment;
+    // We need to keep track of whether startActivityForResult originated from a Fragment, so we
+    // can conditionally check whether the requestCode collides with our reserved ID space for the
+    // request index (see above). Unfortunately we can't just call
+    // super.startActivityForResult(...) to bypass the check when the call didn't come from a
+    // fragment, since we need to use the ActivityCompat version for backward compatibility.
+    boolean mStartedActivityFromFragment;
+
     // A hint for the next candidate request index. Request indicies are ints between 0 and 2^16-1
     // which are encoded into the upper 16 bits of the requestCode for
     // Fragment.startActivityForResult(...) calls. This allows us to dispatch onActivityResult(...)
@@ -377,6 +381,23 @@
     }
 
     @Override
+    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+        final View v = dispatchFragmentsOnCreateView(parent, name, context, attrs);
+        if (v == null) {
+            return super.onCreateView(parent, name, context, attrs);
+        }
+        return v;
+    }
+
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        final View v = dispatchFragmentsOnCreateView(null, name, context, attrs);
+        if (v == null) {
+            return super.onCreateView(name, context, attrs);
+        }
+        return v;
+    }
+
     final View dispatchFragmentsOnCreateView(View parent, String name, Context context,
             AttributeSet attrs) {
         return mFragments.onCreateView(parent, name, context, attrs);
@@ -759,6 +780,59 @@
     }
 
     @Override
+    public void startActivityForResult(Intent intent, int requestCode,
+            @Nullable Bundle options) {
+        // If this was started from a Fragment we've already checked the upper 16 bits were not in
+        // use, and then repurposed them for the Fragment's index.
+        if (!mStartedActivityFromFragment) {
+            if (requestCode != -1) {
+                checkForValidRequestCode(requestCode);
+            }
+        }
+        super.startActivityForResult(intent, requestCode, options);
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+            throws IntentSender.SendIntentException {
+        // If this was started from a Fragment we've already checked the upper 16 bits were not in
+        // use, and then repurposed them for the Fragment's index.
+        if (!mStartedIntentSenderFromFragment) {
+            if (requestCode != -1) {
+                checkForValidRequestCode(requestCode);
+            }
+        }
+        super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
+                extraFlags);
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
+        // If this was started from a Fragment we've already checked the upper 16 bits were not in
+        // use, and then repurposed them for the Fragment's index.
+        if (!mStartedIntentSenderFromFragment) {
+            if (requestCode != -1) {
+                checkForValidRequestCode(requestCode);
+            }
+        }
+        super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
+                extraFlags, options);
+    }
+
+    /**
+     * Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
+     * an {@link IllegalArgumentException} if the code is not valid.
+     */
+    static void checkForValidRequestCode(int requestCode) {
+        if ((requestCode & 0xffff0000) != 0) {
+            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
+        }
+    }
+
+    @Override
     public final void validateRequestPermissionsRequestCode(int requestCode) {
         // We use 16 bits of the request code to encode the fragment id when
         // requesting permissions from a fragment. Hence, requestPermissions()
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index cd673d4..beb07f8 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -24,7 +24,6 @@
 import android.animation.AnimatorSet;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
-import android.arch.lifecycle.ViewModelStore;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources.NotFoundException;
@@ -64,6 +63,7 @@
 import androidx.core.util.LogWriter;
 import androidx.core.util.Pair;
 import androidx.core.view.ViewCompat;
+import androidx.lifecycle.ViewModelStore;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentManagerNonConfig.java b/fragment/src/main/java/androidx/fragment/app/FragmentManagerNonConfig.java
index c006f46..61553d6 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentManagerNonConfig.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentManagerNonConfig.java
@@ -17,9 +17,10 @@
 
 package androidx.fragment.app;
 
-import android.arch.lifecycle.ViewModelStore;
 import android.os.Parcelable;
 
+import androidx.lifecycle.ViewModelStore;
+
 import java.util.List;
 
 /**
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentState.java b/fragment/src/main/java/androidx/fragment/app/FragmentState.java
index 2d94daf..d2fcb2a 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentState.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentState.java
@@ -16,13 +16,14 @@
 
 package androidx.fragment.app;
 
-import android.arch.lifecycle.ViewModelStore;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
 
+import androidx.lifecycle.ViewModelStore;
+
 final class FragmentState implements Parcelable {
     final String mClassName;
     final int mIndex;
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt
index dcb6635..61415da 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt
@@ -85,7 +85,8 @@
         fun createProcessor(
             config: Config,
             reversedMode: Boolean = false,
-            rewritingSupportLib: Boolean = false
+            rewritingSupportLib: Boolean = false,
+            useIdentityIfTypeIsMissing: Boolean = true
         ): Processor {
             var newConfig = config
 
@@ -101,7 +102,11 @@
                 )
             }
 
-            val context = TransformationContext(newConfig, rewritingSupportLib, reversedMode)
+            val context = TransformationContext(
+                config = newConfig,
+                rewritingSupportLib = rewritingSupportLib,
+                isInReversedMode = reversedMode,
+                useIdentityIfTypeIsMissing = useIdentityIfTypeIsMissing)
             val transformers = if (rewritingSupportLib) {
                 createSLTransformers(context)
             } else {
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt
index e6739f0..3564ff3 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt
@@ -111,18 +111,25 @@
 
     object Builder {
 
+        /**
+         * @param recursive Whether nested archives should be also extracted.
+         */
         @Throws(IOException::class)
-        fun extract(archiveFile: File): Archive {
+        fun extract(archiveFile: File, recursive: Boolean = true): Archive {
             Log.i(TAG, "Extracting: %s", archiveFile.absolutePath)
 
             val inputStream = FileInputStream(archiveFile)
             inputStream.use {
-                return extractArchive(it, archiveFile.toPath())
+                return extractArchive(it, archiveFile.toPath(), recursive)
             }
         }
 
         @Throws(IOException::class)
-        private fun extractArchive(inputStream: InputStream, relativePath: Path): Archive {
+        private fun extractArchive(
+                inputStream: InputStream,
+                relativePath: Path,
+                recursive: Boolean
+        ): Archive {
             val zipIn = ZipInputStream(inputStream)
             val files = mutableListOf<ArchiveItem>()
 
@@ -131,9 +138,9 @@
             while (entry != null) {
                 if (!entry.isDirectory) {
                     val entryPath = Paths.get(entry.name)
-                    if (isArchive(entry)) {
+                    if (isArchive(entry) && recursive) {
                         Log.i(TAG, "Extracting nested: %s", entryPath)
-                        files.add(extractArchive(zipIn, entryPath))
+                        files.add(extractArchive(zipIn, entryPath, recursive))
                     } else {
                         files.add(extractFile(zipIn, entryPath))
                     }
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt
index 200f7f5..734f46d 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt
@@ -69,5 +69,16 @@
         return JavaType(tokens.joinToString("$"))
     }
 
+    /**
+     * Returns parent type of this types (e.g. for test.Class.InnerClass -> returns test.Class). For
+     * top level packages returns identity.
+     */
+    fun getParentType(): JavaType {
+        if (fullName.contains("/")) {
+            return JavaType(fullName.substringBeforeLast('/'))
+        }
+        return this
+    }
+
     override fun toString() = fullName
 }
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
index 2b0d291..d6705af 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
@@ -28,7 +28,11 @@
 class TransformationContext(
     val config: Config,
     val rewritingSupportLib: Boolean = false,
-    val isInReversedMode: Boolean = false
+    val isInReversedMode: Boolean = false,
+    /**
+     * Whether to use identity if type in our scope is missing instead of throwing an exception.
+     */
+    val useIdentityIfTypeIsMissing: Boolean = true
 ) {
 
     // Merges all packages prefixes into one regEx pattern
@@ -36,11 +40,6 @@
         "^(" + config.restrictToPackagePrefixes.map { "($it)" }.joinToString("|") + ").*$")
 
     /**
-     * Whether to use identity if type in our scope is missing instead of throwing an exception.
-     */
-    val useIdentityIfTypeIsMissing = rewritingSupportLib || isInReversedMode
-
-    /**
      * Whether to skip verification of dependency version match in pom files.
      */
     val ignorePomVersionCheck = rewritingSupportLib || isInReversedMode
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
index 5a4a130..dc400e3 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
@@ -66,19 +66,36 @@
     }
 
     override fun rewriteString(value: String): String {
+        val startsWithAndroidX = context.isInReversedMode && value.startsWith("androidx")
+
         val type = JavaType.fromDotVersion(value)
         if (!context.isEligibleForRewrite(type)) {
+            if (startsWithAndroidX) {
+                Log.i(TAG, "Found string '%s' but failed to rewrite", value)
+            }
             return value
         }
 
         val result = typesMap.mapType(type)
         if (result != null) {
             changesDone = changesDone || result != type
-            Log.i(TAG, "  map string: %s -> %s", type, result)
+            Log.i(TAG, "Map string: '%s' -> '%s'", type, result)
             return result.toDotNotation()
         }
 
+        // We might be working with an internal type or field reference, e.g.
+        // AccessibilityNodeInfoCompat.PANE_TITLE_KEY. So we try to remove last segment to help it.
+        if (value.contains(".")) {
+            val subTypeResult = typesMap.mapType(type.getParentType())
+            if (subTypeResult != null) {
+                val result = subTypeResult.toDotNotation() + '.' + value.substringAfterLast('.')
+                Log.i(TAG, "Map string: '%s' -> '%s' via fallback", value, result)
+                return result
+            }
+        }
+
         // We do not treat string content mismatches as errors
+        Log.i(TAG, "Found string '%s' but failed to rewrite", value)
         return value
     }
 
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt
index f923198..1c10e02 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt
@@ -80,6 +80,11 @@
      * Changes are not saved back until requested.
      */
     fun applyRules(context: TransformationContext) {
+        if (context.rewritingSupportLib) {
+            rewriteOwnArtifactInfo(context)
+            hasChanged = true
+        }
+
         if (dependenciesGroup == null) {
             // Nothing to transform as this file has no dependencies section
             return
@@ -87,32 +92,10 @@
 
         val newDependencies = mutableSetOf<PomDependency>()
         for (dependency in dependencies) {
-            if (dependency.shouldSkipRewrite()) {
-                continue
-            }
-
-            val rule = context.config.pomRewriteRules.firstOrNull { it.matches(dependency) }
-            if (rule != null) {
-                // Replace with new dependencies
-                newDependencies.addAll(rule.to.mapTo(newDependencies) { it.rewrite(dependency) })
-                continue
-            }
-
-            val matchesPrefix = context.config.restrictToPackagePrefixesWithDots.any {
-                dependency.groupId!!.startsWith(it)
-            }
-            if (matchesPrefix) {
-                // Report error
-                Log.e(TAG, "No mapping found for '%s'", dependency.toStringNotation())
-                context.reportNoPackageMappingFoundFailure()
-            }
-
-            // No rule to rewrite => keep it
-            newDependencies.add(dependency)
+            newDependencies.addAll(mapDependency(dependency, context))
         }
 
         if (newDependencies.isEmpty()) {
-            // No changes
             return
         }
 
@@ -121,6 +104,72 @@
         hasChanged = true
     }
 
+    fun getAsPomDependency(): PomDependency {
+        val groupIdNode = document.rootElement
+                .getChild("groupId", document.rootElement.namespace)
+        val artifactIdNode = document.rootElement
+                .getChild("artifactId", document.rootElement.namespace)
+        val version = document.rootElement
+                .getChild("version", document.rootElement.namespace)
+
+        return PomDependency(groupIdNode.text, artifactIdNode.text, version.text)
+    }
+
+    private fun rewriteOwnArtifactInfo(context: TransformationContext) {
+        val groupIdNode = document.rootElement
+                .getChild("groupId", document.rootElement.namespace)
+        val artifactIdNode = document.rootElement
+                .getChild("artifactId", document.rootElement.namespace)
+        val version = document.rootElement
+                .getChild("version", document.rootElement.namespace)
+
+        if (groupIdNode == null || artifactIdNode == null || version == null) {
+            return
+        }
+
+        val dependency = PomDependency(groupIdNode.text, artifactIdNode.text, version.text)
+        val newDependency = mapDependency(dependency, context).first()
+
+        if (newDependency != dependency) {
+            groupIdNode.text = newDependency.groupId
+            artifactIdNode.text = newDependency.artifactId
+            version.text = newDependency.version
+        }
+    }
+
+    private fun mapDependency(
+            dependency: PomDependency,
+            context: TransformationContext
+    ): Set<PomDependency> {
+        if (dependency.shouldSkipRewrite()) {
+            return emptySet()
+        }
+
+        val rule = context.config.pomRewriteRules.firstOrNull { it.matches(dependency) }
+        if (rule != null) {
+            // Replace with new dependencies
+            return rule.to.map { it.rewrite(dependency) }.toSet()
+        }
+
+        val matchesPrefix = context.config.restrictToPackagePrefixesWithDots.any {
+            dependency.groupId!!.startsWith(it)
+        }
+
+        if (matchesPrefix) {
+            if (context.useIdentityIfTypeIsMissing) {
+                Log.i(TAG, "No mapping found for '%s' - using identity",
+                        dependency.toStringNotation())
+            } else {
+                // Report error
+                Log.e(TAG, "No mapping found for '%s'", dependency.toStringNotation())
+                context.reportNoPackageMappingFoundFailure()
+            }
+        }
+
+        // No rule to rewrite => keep it
+        return setOf(dependency)
+    }
+
     /**
      * Saves any current pending changes back to the file if needed.
      */
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
index dc63495..c7c6b1d 100644
--- a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
@@ -183,8 +183,14 @@
         if (result != null) {
             return result.toDotNotation()
         }
+        if (context.useIdentityIfTypeIsMissing) {
+            Log.i(TAG, "No mapping for package: '%s' in artifact: '%s' - using identity",
+                    pckg, archiveName)
+            return packageName
+        }
+
         context.reportNoPackageMappingFoundFailure()
-        Log.e(TAG, "No mapping for package: '$pckg' in artifact: '$archiveName'")
+        Log.e(TAG, "No mapping for package: '%s' in artifact: '%s'", pckg, archiveName)
         return packageName
     }
 }
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
index 26f642a..748aa8f 100644
--- a/jetifier/jetifier/core/src/main/resources/default.config
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -420,10 +420,6 @@
             to: "androidx/media/AudioAttributesCompat{0}"
         },
         {
-            from: "android/support/v4/media/MediaBrowserCompat(.*)",
-            to: "androidx/media/MediaBrowserCompat{0}"
-        },
-        {
             from: "android/support/v4/media/MediaBrowserCompatUtils(.*)",
             to: "androidx/media/MediaBrowserCompatUtils{0}"
         },
@@ -444,10 +440,6 @@
             to: "androidx/media/session/MediaButtonReceiver{0}"
         },
         {
-            from: "android/support/v4/media/session/MediaControllerCompat(.*)",
-            to: "androidx/media/session/MediaControllerCompat{0}"
-        },
-        {
             from: "android/support/v4/media/(.*)",
             to: "android/support/v4/media/{0}"
         },
@@ -831,7 +823,7 @@
             to: [{ groupId: "androidx.asynclayoutinflater", artifactId: "asynclayoutinflater", version: "1.0.0" }]
         },
         {
-            from: { groupId: "com.android.support", artifactId: "collection", version: "28.0.0" },
+            from: { groupId: "com.android.support", artifactId: "collections", version: "28.0.0" },
             to: [{ groupId: "androidx.collection", artifactId: "collection", version: "1.0.0" }]
         },
         {
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
index 5491498..2fd93ad 100644
--- a/jetifier/jetifier/core/src/main/resources/default.generated.config
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -1187,7 +1187,7 @@
     {
       "from": {
         "groupId": "com.android.support",
-        "artifactId": "collection",
+        "artifactId": "collections",
         "version": "28.0.0"
       },
       "to": [
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/rules/JavaTypeTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/rules/JavaTypeTest.kt
new file mode 100644
index 0000000..a5c0e89
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/rules/JavaTypeTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 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.support.tools.jetifier.core.rules
+
+import com.google.common.truth.Truth
+import org.junit.Test
+
+class JavaTypeTest {
+    @Test fun javaType_testFromDotVersion() {
+        val type = JavaType.fromDotVersion("test.MyClass.FIELD")
+
+        Truth.assertThat(type.fullName).isEqualTo("test/MyClass/FIELD")
+    }
+
+    @Test fun javaType_testParent() {
+        val type = JavaType.fromDotVersion("test.MyClass.FIELD")
+        val result = type.getParentType().toDotNotation()
+
+        Truth.assertThat(result).isEqualTo("test.MyClass")
+    }
+
+    @Test fun javaType_testParent_identity() {
+        val type = JavaType.fromDotVersion("test")
+        val result = type.getParentType().toDotNotation()
+
+        Truth.assertThat(result).isEqualTo("test")
+    }
+
+    @Test fun javaType_remapeWithNewRootType() {
+        val type = JavaType.fromDotVersion("test.MyClass\$Inner")
+        val remapWith = JavaType.fromDotVersion("hello.NewClass")
+
+        Truth.assertThat(type.remapWithNewRootType(remapWith).toDotNotation())
+                .isEqualTo("hello.NewClass\$Inner")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImplTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImplTest.kt
new file mode 100644
index 0000000..4dcbd39
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImplTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018 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.support.tools.jetifier.core.transform.bytecode
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.PackageMap
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.objectweb.asm.ClassWriter
+
+class CoreRemapperImplTest {
+
+    @Test
+    fun remapString_shouldUseFallbackForField() {
+        val remapper = prepareRemapper(
+            TypesMap(mapOf(
+                JavaType.fromDotVersion("androidx.test.InputConnectionCompat")
+                    to JavaType.fromDotVersion("android.support.test.InputConnectionCompat")
+            )),
+            "androidx/")
+
+        val given = "androidx.test.InputConnectionCompat.CONTENT_URI"
+        val expected = "android.support.test.InputConnectionCompat.CONTENT_URI"
+
+        Truth.assertThat(remapper.rewriteString(given)).isEqualTo(expected)
+    }
+
+    private fun prepareRemapper(
+            typesMap: TypesMap,
+            restrictToPackagePrefix: String? = null
+    ): CoreRemapperImpl {
+        val prefixes = if (restrictToPackagePrefix == null) {
+            emptyList()
+        } else {
+            listOf(restrictToPackagePrefix)
+        }
+
+        val config = Config(
+                restrictToPackagePrefixes = prefixes,
+                rewriteRules = emptyList(),
+                typesMap = typesMap,
+                slRules = emptyList(),
+                pomRewriteRules = emptySet(),
+                proGuardMap = ProGuardTypesMap.EMPTY,
+                packageMap = PackageMap.EMPTY)
+
+        val context = TransformationContext(config,
+                isInReversedMode = true)
+
+        val writer = ClassWriter(0 /* flags */)
+        return CoreRemapperImpl(context, writer)
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteInZipTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteInZipTest.kt
new file mode 100644
index 0000000..77dc196
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteInZipTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 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.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.FileMapping
+import android.support.tools.jetifier.core.Processor
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.common.truth.Truth
+import org.junit.Test
+import java.io.File
+
+/**
+ * Tests that pom file inside a zip file was correctly rewritten.
+ */
+class PomRewriteInZipTest {
+
+    companion object {
+        private val TEST_CONFIG = Config(
+            restrictToPackagePrefixes = listOf("com/sample"),
+            rewriteRules = listOf(),
+            slRules = listOf(),
+            pomRewriteRules = setOf(
+                PomRewriteRule(
+                    from = PomDependency(
+                        groupId = "old.group",
+                        artifactId = "myOldArtifact",
+                        version = "0.1.0"),
+                to = setOf(
+                        PomDependency(
+                            groupId = "com.sample.my.group",
+                            artifactId = "myArtifact",
+                            version = "1.0.0"
+                        )
+                    )
+                )),
+            typesMap = TypesMap.EMPTY,
+            proGuardMap = ProGuardTypesMap.EMPTY
+        )
+    }
+
+    @Test fun rewritePomInZip_rewritingSL_shouldRewrite() {
+        val inputZipPath = "/pomRefactorTest/pomTest.zip"
+
+        val processor = Processor.createProcessor(
+            TEST_CONFIG,
+            rewritingSupportLib = true,
+            reversedMode = true)
+
+        val inputFile = File(javaClass.getResource(inputZipPath).file)
+
+        val tempDir = createTempDir()
+        val expectedFile = File(createTempDir(), "test.zip")
+
+        val resultFiles = processor.transform(setOf(FileMapping(inputFile, expectedFile)))
+
+        Truth.assertThat(resultFiles).hasSize(1)
+
+        val returnedArchive = Archive.Builder.extract(expectedFile)
+        val returnedPom = returnedArchive.files.first() as ArchiveFile
+        val content = String(returnedPom.data)
+
+        Truth.assertThat(returnedPom.fileName).isEqualTo("test.pom")
+
+        Truth.assertThat(content).doesNotContain("com.sample.my.group")
+        Truth.assertThat(content).doesNotContain("myArtifact")
+        Truth.assertThat(content).doesNotContain("1.0.0")
+
+        Truth.assertThat(content).contains("old.group")
+        Truth.assertThat(content).contains("myOldArtifact")
+        Truth.assertThat(content).contains("0.1.0")
+
+        tempDir.delete()
+    }
+
+    @Test fun rewritePomInZip_notRewritingSL_shouldNotRewrite() {
+        val inputZipPath = "/pomRefactorTest/pomTest.zip"
+
+        val processor = Processor.createProcessor(
+                TEST_CONFIG,
+                rewritingSupportLib = false,
+                reversedMode = true)
+
+        val inputFile = File(javaClass.getResource(inputZipPath).file)
+
+        val tempDir = createTempDir()
+        val expectedFile = File(createTempDir(), "test.zip")
+
+        val resultFiles = processor.transform(setOf(FileMapping(inputFile, expectedFile)))
+
+        Truth.assertThat(resultFiles).hasSize(1)
+
+        val returnedArchive = Archive.Builder.extract(expectedFile)
+        val returnedPom = returnedArchive.files.first() as ArchiveFile
+        val content = String(returnedPom.data)
+
+        Truth.assertThat(returnedPom.fileName).isEqualTo("test.pom")
+
+        Truth.assertThat(content).contains("com.sample.my.group")
+        Truth.assertThat(content).contains("myArtifact")
+        Truth.assertThat(content).contains("1.0.0")
+
+        Truth.assertThat(content).doesNotContain("old.group")
+        Truth.assertThat(content).doesNotContain("myOldArtifact")
+        Truth.assertThat(content).doesNotContain("0.1.0")
+
+        tempDir.delete()
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
index d4acfb6..7d13826 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
@@ -100,7 +100,7 @@
     class ProGuardTesterForType(private val config: Config, private val given: String) {
 
         fun getsRewrittenTo(expectedType: String) {
-            val context = TransformationContext(config)
+            val context = TransformationContext(config, useIdentityIfTypeIsMissing = false)
             val mapper = ProGuardTypesMapper(context)
             val result = mapper.replaceType(given)
 
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt
index 44f2ba4..3549559 100644
--- a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt
@@ -330,7 +330,10 @@
             proGuardMap = ProGuardTypesMap.EMPTY,
             packageMap = packageMap
         )
-        val context = TransformationContext(config, rewritingSupportLib = rewritingSupportLib)
+        val context = TransformationContext(
+            config,
+            rewritingSupportLib = rewritingSupportLib,
+            useIdentityIfTypeIsMissing = false)
         context.libraryName = libraryName
         val processor = XmlResourcesTransformer(context)
         val fileName = if (isManifestFile) {
diff --git a/jetifier/jetifier/core/src/test/resources/pomRefactorTest/pomTest.zip b/jetifier/jetifier/core/src/test/resources/pomRefactorTest/pomTest.zip
new file mode 100644
index 0000000..4b2270b
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/resources/pomRefactorTest/pomTest.zip
Binary files differ
diff --git a/jetifier/jetifier/standalone/build.gradle b/jetifier/jetifier/standalone/build.gradle
index 3caa366..eed727c 100644
--- a/jetifier/jetifier/standalone/build.gradle
+++ b/jetifier/jetifier/standalone/build.gradle
@@ -31,5 +31,5 @@
 
   destinationDir rootProject.distDir // defined by support library plugin
 }
-rootProject.tasks["buildOnServer"].dependsOn(dist)
+rootProject.tasks["createArchive"].dependsOn(dist)
 
diff --git a/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt
index e840a01..e426621 100644
--- a/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt
+++ b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt
@@ -43,13 +43,17 @@
         val OPTION_OUTPUT_FILE = createOption("outputfile", "Output file", isRequired = false)
         val OPTION_CONFIG = createOption("c", "Input config path", isRequired = false)
         val OPTION_LOG_LEVEL = createOption("l", "Logging level. debug, verbose, error, info " +
-            "(default)", isRequired = false)
+                "(default)", isRequired = false)
         val OPTION_REVERSED = createOption("r", "Run reversed process", hasArgs = false,
-            isRequired = false)
+                isRequired = false)
         val OPTION_REWRITE_SUPPORT_LIB = createOption("s", "If set, all libraries being rewritten" +
-            " are assumed to be part of Support Library. Otherwise only general dependencies are" +
-            " expected.",
-            hasArgs = false, isRequired = false)
+                " are assumed to be part of Support Library. Otherwise only general dependencies " +
+                " are expected.", hasArgs = false, isRequired = false)
+        val OPTION_STRICT = createOption("strict",
+                "Don't fallback in case rules are missing", hasArgs = false, isRequired = false)
+        val OPTION_REBUILD_TOP_OF_TREE = createOption("rebuildTopOfTree",
+                "Rebuild the zip of maven distribution according to the generated pom file",
+                hasArgs = false, isRequired = false)
 
         private fun createOption(
             argName: String,
@@ -80,6 +84,8 @@
         val inputLibraries = cmd.getOptionValues(OPTION_INPUT.opt).map { File(it) }.toSet()
         val outputDir = cmd.getOptionValue(OPTION_OUTPUT_DIR.opt)
         val outputFile = cmd.getOptionValue(OPTION_OUTPUT_FILE.opt)
+        val rebuildTopOfTree = cmd.hasOption(OPTION_REBUILD_TOP_OF_TREE.opt)
+
         if (outputDir == null && outputFile == null) {
             throw IllegalArgumentException("Must specify -outputdir or -outputfile")
         }
@@ -93,8 +99,12 @@
 
         val fileMappings = mutableSetOf<FileMapping>()
         if (outputFile != null) {
-            fileMappings.add(FileMapping(inputLibraries.first(),
-                    File(outputFile)))
+            if (rebuildTopOfTree) {
+                val tempFile = createTempFile(suffix = "zip")
+                fileMappings.add(FileMapping(inputLibraries.first(), tempFile))
+            } else {
+                fileMappings.add(FileMapping(inputLibraries.first(), File(outputFile)))
+            }
         } else {
             inputLibraries.forEach {
                 val newFileName = File(Paths.get(outputDir).toString(), it.name)
@@ -118,11 +128,21 @@
 
         val isReversed = cmd.hasOption(OPTION_REVERSED.opt)
         val rewriteSupportLib = cmd.hasOption(OPTION_REWRITE_SUPPORT_LIB.opt)
+        val isStrict = cmd.hasOption(OPTION_STRICT.opt)
         val processor = Processor.createProcessor(
             config = config,
             reversedMode = isReversed,
-            rewritingSupportLib = rewriteSupportLib)
+            rewritingSupportLib = rewriteSupportLib,
+            useIdentityIfTypeIsMissing = !isStrict)
         processor.transform(fileMappings)
+
+        if (rebuildTopOfTree) {
+            val tempFile = fileMappings.first().to
+            TopOfTreeBuilder().rebuildFrom(
+                inputZip = tempFile,
+                outputZip = File(outputFile))
+            tempFile.delete()
+        }
     }
 
     private fun parseCmdLine(args: Array<String>): CommandLine? {
diff --git a/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/TopOfTreeBuilder.kt b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/TopOfTreeBuilder.kt
new file mode 100644
index 0000000..893ee65
--- /dev/null
+++ b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/TopOfTreeBuilder.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 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.support.tools.jetifier.standalone
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.archive.ArchiveItemVisitor
+import android.support.tools.jetifier.core.transform.pom.PomDocument
+import java.io.File
+import java.nio.file.Paths
+import java.security.MessageDigest
+
+/**
+ * Utility to rebuild de-jetified zipped maven repo.
+ */
+class TopOfTreeBuilder {
+
+    companion object {
+        const val DIR_PREFIX = "m2repository"
+    }
+
+    fun rebuildFrom(inputZip: File, outputZip: File) {
+        val archive = Archive.Builder.extract(inputZip, recursive = false)
+
+        // Find poms
+        val pomFilter = FileFilter({ it.isPomFile() })
+        archive.accept(pomFilter)
+        val pomFiles = pomFilter.files
+
+        // Find archives
+        val archivesFilter = FileFilter({
+            return@FileFilter it.fileName.endsWith(".aar")
+                    || (it.fileName.endsWith("jar")
+                    && !it.fileName.contains("sources") && !it.fileName.contains("javadoc"))
+        })
+        archive.accept(archivesFilter)
+        val libFiles = archivesFilter.files
+
+        // Process
+        val newFiles = mutableSetOf<ArchiveFile>()
+        pomFiles.forEach {
+            pomFile -> run {
+            val name = pomFile.relativePath.toFile().nameWithoutExtension
+            val artifactFile = libFiles.first { it.relativePath.toString().contains(name) }
+            process(pomFile, artifactFile, newFiles)
+        }
+        }
+
+        // Write the result
+        val finalArchive = Archive(outputZip.toPath(), newFiles.toList())
+        finalArchive.writeSelf()
+    }
+
+    private fun process(
+            pomFile: ArchiveFile,
+            artifactFile: ArchiveFile,
+            resultSet: MutableSet<ArchiveFile>
+    ) {
+        val pomDep = PomDocument.loadFrom(pomFile).getAsPomDependency()
+
+        val groupAsPath = pomDep.groupId!!.replace('.', '/')
+
+        val packaging = artifactFile.relativePath.toFile().extension
+        val baseFileName = "${pomDep.artifactId}-${pomDep.version}"
+
+        val artifactDir = Paths.get(
+                DIR_PREFIX, groupAsPath, pomDep.artifactId, pomDep.version!!.toUpperCase())
+        val newLibFilePath = Paths.get(artifactDir.toString(), "$baseFileName.$packaging")
+        val newPomFilePath = Paths.get(artifactDir.toString(), "$baseFileName.pom")
+
+        val newArtifactFile = ArchiveFile(newLibFilePath, artifactFile.data)
+        val newPomFile = ArchiveFile(newPomFilePath, pomFile.data)
+
+        resultSet.add(newArtifactFile)
+        resultSet.add(getHashFileOf(newArtifactFile, "MD5"))
+        resultSet.add(getHashFileOf(newArtifactFile, "SHA1"))
+
+        resultSet.add(newPomFile)
+        resultSet.add(getHashFileOf(newPomFile, "MD5"))
+        resultSet.add(getHashFileOf(newPomFile, "SHA1"))
+    }
+
+    private fun getHashFileOf(file: ArchiveFile, hashType: String): ArchiveFile {
+        val md = MessageDigest.getInstance(hashType)
+        val result = md.digest(file.data)
+        return ArchiveFile(Paths.get(
+                file.relativePath.toString() + "." + hashType.toLowerCase()), result)
+    }
+
+    private class FileFilter(private val filter: (ArchiveFile) -> Boolean) : ArchiveItemVisitor {
+
+        val files = mutableSetOf<ArchiveFile>()
+
+        override fun visit(archiveFile: ArchiveFile) {
+            if (filter(archiveFile)) {
+                files.add(archiveFile)
+            }
+        }
+
+        override fun visit(archive: Archive) {
+            archive.files.forEach { it.accept(this) }
+        }
+    }
+}
\ No newline at end of file
diff --git a/leanback/api/current.txt b/leanback/api/current.txt
index e13df6d..138509e 100644
--- a/leanback/api/current.txt
+++ b/leanback/api/current.txt
@@ -1004,9 +1004,9 @@
 package androidx.leanback.media {
 
   public class MediaControllerAdapter extends androidx.leanback.media.PlayerAdapter {
-    ctor public MediaControllerAdapter(androidx.media.session.MediaControllerCompat);
+    ctor public MediaControllerAdapter(android.support.v4.media.session.MediaControllerCompat);
     method public android.graphics.drawable.Drawable getMediaArt(android.content.Context);
-    method public androidx.media.session.MediaControllerCompat getMediaController();
+    method public android.support.v4.media.session.MediaControllerCompat getMediaController();
     method public java.lang.CharSequence getMediaSubtitle();
     method public java.lang.CharSequence getMediaTitle();
     method public void pause();
@@ -1015,12 +1015,12 @@
 
   public abstract deprecated class MediaControllerGlue extends androidx.leanback.media.PlaybackControlGlue {
     ctor public MediaControllerGlue(android.content.Context, int[], int[]);
-    method public void attachToMediaController(androidx.media.session.MediaControllerCompat);
+    method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
     method public void detach();
     method public int getCurrentPosition();
     method public int getCurrentSpeedId();
     method public android.graphics.drawable.Drawable getMediaArt();
-    method public final androidx.media.session.MediaControllerCompat getMediaController();
+    method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
     method public int getMediaDuration();
     method public java.lang.CharSequence getMediaSubtitle();
     method public java.lang.CharSequence getMediaTitle();
diff --git a/leanback/src/androidTest/java/androidx/leanback/media/MediaControllerAdapterTest.java b/leanback/src/androidTest/java/androidx/leanback/media/MediaControllerAdapterTest.java
index 0baeb9f..eba9864 100644
--- a/leanback/src/androidTest/java/androidx/leanback/media/MediaControllerAdapterTest.java
+++ b/leanback/src/androidTest/java/androidx/leanback/media/MediaControllerAdapterTest.java
@@ -34,11 +34,11 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 
 import androidx.leanback.widget.PlaybackControlsRow;
-import androidx.media.session.MediaControllerCompat;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/leanback/src/main/java/androidx/leanback/media/MediaControllerAdapter.java b/leanback/src/main/java/androidx/leanback/media/MediaControllerAdapter.java
index 16d78c4..e4fe997 100644
--- a/leanback/src/main/java/androidx/leanback/media/MediaControllerAdapter.java
+++ b/leanback/src/main/java/androidx/leanback/media/MediaControllerAdapter.java
@@ -30,11 +30,11 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
 import androidx.leanback.widget.PlaybackControlsRow;
-import androidx.media.session.MediaControllerCompat;
 
 /**
  * A helper class for implementing a adapter layer for {@link MediaControllerCompat}.
diff --git a/leanback/src/main/java/androidx/leanback/media/MediaControllerGlue.java b/leanback/src/main/java/androidx/leanback/media/MediaControllerGlue.java
index f9cdefc..2a7977c 100644
--- a/leanback/src/main/java/androidx/leanback/media/MediaControllerGlue.java
+++ b/leanback/src/main/java/androidx/leanback/media/MediaControllerGlue.java
@@ -22,11 +22,10 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
-import androidx.media.session.MediaControllerCompat;
-
 /**
  * A helper class for implementing a glue layer for {@link MediaControllerCompat}.
  * @deprecated Use {@link MediaControllerAdapter} with {@link PlaybackTransportControlGlue} or
diff --git a/lifecycle/common-java8/api/1.0.0.txt b/lifecycle/common-java8/api_legacy/1.0.0.txt
similarity index 100%
rename from lifecycle/common-java8/api/1.0.0.txt
rename to lifecycle/common-java8/api_legacy/1.0.0.txt
diff --git a/lifecycle/common-java8/build.gradle b/lifecycle/common-java8/build.gradle
index 0f4ffd8..205ed87 100644
--- a/lifecycle/common-java8/build.gradle
+++ b/lifecycle/common-java8/build.gradle
@@ -24,7 +24,7 @@
 }
 
 dependencies {
-    compile(project(":lifecycle:common"))
+    compile(project(":lifecycle:lifecycle-common"))
     compile(SUPPORT_ANNOTATIONS)
 
     testCompile(JUNIT)
diff --git a/lifecycle/common-java8/src/main/java/android/arch/lifecycle/DefaultLifecycleObserver.java b/lifecycle/common-java8/src/main/java/android/arch/lifecycle/DefaultLifecycleObserver.java
deleted file mode 100644
index b6f468c..0000000
--- a/lifecycle/common-java8/src/main/java/android/arch/lifecycle/DefaultLifecycleObserver.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import 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/lifecycle/common-java8/src/main/java/androidx/lifecycle/DefaultLifecycleObserver.java b/lifecycle/common-java8/src/main/java/androidx/lifecycle/DefaultLifecycleObserver.java
new file mode 100644
index 0000000..f10decb
--- /dev/null
+++ b/lifecycle/common-java8/src/main/java/androidx/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 androidx.lifecycle;
+
+import androidx.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/lifecycle/common/api/0.0.0.txt b/lifecycle/common/api_legacy/0.0.0.txt
similarity index 100%
rename from lifecycle/common/api/0.0.0.txt
rename to lifecycle/common/api_legacy/0.0.0.txt
diff --git a/lifecycle/common/api/1.0.0.txt b/lifecycle/common/api_legacy/1.0.0.txt
similarity index 100%
rename from lifecycle/common/api/1.0.0.txt
rename to lifecycle/common/api_legacy/1.0.0.txt
diff --git a/lifecycle/common/api/current.txt b/lifecycle/common/api_legacy/current.txt
similarity index 100%
rename from lifecycle/common/api/current.txt
rename to lifecycle/common/api_legacy/current.txt
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/ClassesInfoCache.java b/lifecycle/common/src/main/java/android/arch/lifecycle/ClassesInfoCache.java
deleted file mode 100644
index d88e276..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/ClassesInfoCache.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import 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 = getDeclaredMethods(klass);
-        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;
-    }
-
-    private Method[] getDeclaredMethods(Class klass) {
-        try {
-            return klass.getDeclaredMethods();
-        } catch (NoClassDefFoundError e) {
-            throw new IllegalArgumentException("The observer class has some methods that use "
-                    + "newer APIs which are not available in the current OS version. Lifecycles "
-                    + "cannot access even other methods so you should make sure that your "
-                    + "observer classes only access framework classes that are available "
-                    + "in your min API level OR use lifecycle:compiler annotation processor.", e);
-        }
-    }
-
-    CallbackInfo getInfo(Class klass) {
-        CallbackInfo existing = mCallbackMap.get(klass);
-        if (existing != null) {
-            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 : getDeclaredMethods(klass);
-        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/lifecycle/common/src/main/java/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java
deleted file mode 100644
index e8cbe7c..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-
-import 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/lifecycle/common/src/main/java/android/arch/lifecycle/FullLifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/FullLifecycleObserver.java
deleted file mode 100644
index f179274..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/FullLifecycleObserver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-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/lifecycle/common/src/main/java/android/arch/lifecycle/FullLifecycleObserverAdapter.java b/lifecycle/common/src/main/java/android/arch/lifecycle/FullLifecycleObserverAdapter.java
deleted file mode 100644
index 0a91a66..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/FullLifecycleObserverAdapter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-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/lifecycle/common/src/main/java/android/arch/lifecycle/GeneratedAdapter.java b/lifecycle/common/src/main/java/android/arch/lifecycle/GeneratedAdapter.java
deleted file mode 100644
index a8862da..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/GeneratedAdapter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import 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/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java
deleted file mode 100644
index 4601478..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/GenericLifecycleObserver.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.annotation.RestrictTo;
-
-/**
- * Internal class that can receive any lifecycle change and dispatch it to the receiver.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-@SuppressWarnings({"WeakerAccess", "unused"})
-public interface GenericLifecycleObserver extends LifecycleObserver {
-    /**
-     * Called when a state transition event happens.
-     *
-     * @param source The source of the event
-     * @param event The event
-     */
-    void onStateChanged(LifecycleOwner source, Lifecycle.Event event);
-}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java
deleted file mode 100644
index e04cdb4..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycle.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-
-/**
- * Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment}
- * and {@link android.support.v4.app.FragmentActivity FragmentActivity} classes implement
- * {@link LifecycleOwner} interface which has the {@link LifecycleOwner#getLifecycle()
- * getLifecycle} method to access the Lifecycle. You can also implement {@link LifecycleOwner}
- * in your own classes.
- * <p>
- * {@link Event#ON_CREATE}, {@link Event#ON_START}, {@link Event#ON_RESUME} events in this class
- * are dispatched <b>after</b> the {@link LifecycleOwner}'s related method returns.
- * {@link Event#ON_PAUSE}, {@link Event#ON_STOP}, {@link Event#ON_DESTROY} events in this class
- * are dispatched <b>before</b> the {@link LifecycleOwner}'s related method is called.
- * For instance, {@link Event#ON_START} will be dispatched after
- * {@link android.app.Activity#onStart onStart} returns, {@link Event#ON_STOP} will be dispatched
- * before {@link android.app.Activity#onStop onStop} is called.
- * This gives you certain guarantees on which state the owner is in.
- * <p>
- * 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)
- *   void onStopped() {}
- * }
- * </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
- * of type {@link Event}.
- * <pre>
- * class TestObserver implements LifecycleObserver {
- *   {@literal @}OnLifecycleEvent(ON_CREATE)
- *   void onCreated(LifecycleOwner source) {}
- *   {@literal @}OnLifecycleEvent(ON_ANY)
- *   void onAny(LifecycleOwner source, Event event) {}
- * }
- * </pre>
- * These additional parameters are provided to allow you to conveniently observe multiple providers
- * and events without tracking them manually.
- */
-public abstract class Lifecycle {
-    /**
-     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
-     * state.
-     * <p>
-     * The given observer will be brought to the current state of the LifecycleOwner.
-     * For example, if the LifecycleOwner is in {@link State#STARTED} state, the given observer
-     * will receive {@link Event#ON_CREATE}, {@link Event#ON_START} events.
-     *
-     * @param observer The observer to notify.
-     */
-    @MainThread
-    public abstract void addObserver(@NonNull LifecycleObserver observer);
-
-    /**
-     * Removes the given observer from the observers list.
-     * <p>
-     * If this method is called while a state change is being dispatched,
-     * <ul>
-     * <li>If the given observer has not yet received that event, it will not receive it.
-     * <li>If the given observer has more than 1 method that observes the currently dispatched
-     * event and at least one of them received the event, all of them will receive the event and
-     * the removal will happen afterwards.
-     * </ul>
-     *
-     * @param observer The observer to be removed.
-     */
-    @MainThread
-    public abstract void removeObserver(@NonNull LifecycleObserver observer);
-
-    /**
-     * Returns the current state of the Lifecycle.
-     *
-     * @return The current state of the Lifecycle.
-     */
-    @MainThread
-    @NonNull
-    public abstract State getCurrentState();
-
-    @SuppressWarnings("WeakerAccess")
-    public enum Event {
-        /**
-         * Constant for onCreate event of the {@link LifecycleOwner}.
-         */
-        ON_CREATE,
-        /**
-         * Constant for onStart event of the {@link LifecycleOwner}.
-         */
-        ON_START,
-        /**
-         * Constant for onResume event of the {@link LifecycleOwner}.
-         */
-        ON_RESUME,
-        /**
-         * Constant for onPause event of the {@link LifecycleOwner}.
-         */
-        ON_PAUSE,
-        /**
-         * Constant for onStop event of the {@link LifecycleOwner}.
-         */
-        ON_STOP,
-        /**
-         * Constant for onDestroy event of the {@link LifecycleOwner}.
-         */
-        ON_DESTROY,
-        /**
-         * An {@link Event Event} constant that can be used to match all events.
-         */
-        ON_ANY
-    }
-
-    /**
-     * Lifecycle states. You can consider the states as the nodes in a graph and
-     * {@link Event}s as the edges between these nodes.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public enum State {
-        /**
-         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
-         * any more events. For instance, for an {@link android.app.Activity}, this state is reached
-         * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
-         */
-        DESTROYED,
-
-        /**
-         * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
-         * the state when it is constructed but has not received
-         * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
-         */
-        INITIALIZED,
-
-        /**
-         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
-         * is reached in two cases:
-         * <ul>
-         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
-         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
-         * </ul>
-         */
-        CREATED,
-
-        /**
-         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
-         * is reached in two cases:
-         * <ul>
-         *     <li>after {@link android.app.Activity#onStart() onStart} call;
-         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
-         * </ul>
-         */
-        STARTED,
-
-        /**
-         * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
-         * is reached after {@link android.app.Activity#onResume() onResume} is called.
-         */
-        RESUMED;
-
-        /**
-         * Compares if this State is greater or equal to the given {@code state}.
-         *
-         * @param state State to compare with
-         * @return true if this State is greater or equal to the given {@code state}
-         */
-        public boolean isAtLeast(@NonNull State state) {
-            return compareTo(state) >= 0;
-        }
-    }
-}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleObserver.java
deleted file mode 100644
index 94670bf..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleObserver.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-/**
- * Marks a class as a LifecycleObserver. It does not have any methods, instead, relies on
- * {@link OnLifecycleEvent} annotated methods.
- * <p>
- * @see Lifecycle Lifecycle - for samples and usage patterns.
- */
-@SuppressWarnings("WeakerAccess")
-public interface LifecycleObserver {
-
-}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleOwner.java b/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleOwner.java
deleted file mode 100644
index 068bac1..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/LifecycleOwner.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.annotation.NonNull;
-
-/**
- * A class that has an Android lifecycle. These events can be used by custom components to
- * handle lifecycle changes without implementing any code inside the Activity or the Fragment.
- *
- * @see Lifecycle
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-public interface LifecycleOwner {
-    /**
-     * Returns the Lifecycle of the provider.
-     *
-     * @return The lifecycle of the provider.
-     */
-    @NonNull
-    Lifecycle getLifecycle();
-}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycling.java b/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycling.java
deleted file mode 100644
index 7d6b37f..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/Lifecycling.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-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)
-public class Lifecycling {
-
-    private static final int REFLECTIVE_CALLBACK = 1;
-    private static final int GENERATED_CALLBACK = 2;
-
-    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 {
-            return constructor.newInstance(object);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        } catch (InstantiationException e) {
-            throw new RuntimeException(e);
-        } catch (InvocationTargetException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Nullable
-    private static Constructor<? extends GeneratedAdapter> generatedConstructor(Class<?> klass) {
-        try {
-            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);
-            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);
-        }
-    }
-
-    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/lifecycle/common/src/main/java/android/arch/lifecycle/MethodCallsLogger.java b/lifecycle/common/src/main/java/android/arch/lifecycle/MethodCallsLogger.java
deleted file mode 100644
index 031e43e..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/MethodCallsLogger.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import 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/lifecycle/common/src/main/java/android/arch/lifecycle/OnLifecycleEvent.java b/lifecycle/common/src/main/java/android/arch/lifecycle/OnLifecycleEvent.java
deleted file mode 100644
index 9a86b0c..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/OnLifecycleEvent.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@SuppressWarnings("unused")
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface OnLifecycleEvent {
-    Lifecycle.Event value();
-}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
deleted file mode 100644
index f010ed8..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.arch.lifecycle.ClassesInfoCache.CallbackInfo;
-import android.arch.lifecycle.Lifecycle.Event;
-
-/**
- * An internal implementation of {@link GenericLifecycleObserver} that relies on reflection.
- */
-class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
-    private final Object mWrapped;
-    private final CallbackInfo mInfo;
-
-    ReflectiveGenericLifecycleObserver(Object wrapped) {
-        mWrapped = wrapped;
-        mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass());
-    }
-
-    @Override
-    public void onStateChanged(LifecycleOwner source, Event event) {
-        mInfo.invokeCallbacks(source, event, mWrapped);
-    }
-}
diff --git a/lifecycle/common/src/main/java/android/arch/lifecycle/SingleGeneratedAdapterObserver.java b/lifecycle/common/src/main/java/android/arch/lifecycle/SingleGeneratedAdapterObserver.java
deleted file mode 100644
index d176a3a..0000000
--- a/lifecycle/common/src/main/java/android/arch/lifecycle/SingleGeneratedAdapterObserver.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import 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/lifecycle/common/src/main/java/androidx/lifecycle/ClassesInfoCache.java b/lifecycle/common/src/main/java/androidx/lifecycle/ClassesInfoCache.java
new file mode 100644
index 0000000..77d4916
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/ClassesInfoCache.java
@@ -0,0 +1,249 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.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 = getDeclaredMethods(klass);
+        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;
+    }
+
+    private Method[] getDeclaredMethods(Class klass) {
+        try {
+            return klass.getDeclaredMethods();
+        } catch (NoClassDefFoundError e) {
+            throw new IllegalArgumentException("The observer class has some methods that use "
+                    + "newer APIs which are not available in the current OS version. Lifecycles "
+                    + "cannot access even other methods so you should make sure that your "
+                    + "observer classes only access framework classes that are available "
+                    + "in your min API level OR use lifecycle:compiler annotation processor.", e);
+        }
+    }
+
+    CallbackInfo getInfo(Class klass) {
+        CallbackInfo existing = mCallbackMap.get(klass);
+        if (existing != null) {
+            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 : getDeclaredMethods(klass);
+        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/lifecycle/common/src/main/java/androidx/lifecycle/CompositeGeneratedAdaptersObserver.java b/lifecycle/common/src/main/java/androidx/lifecycle/CompositeGeneratedAdaptersObserver.java
new file mode 100644
index 0000000..04e52fc
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/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 androidx.lifecycle;
+
+
+import androidx.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/lifecycle/common/src/main/java/androidx/lifecycle/FullLifecycleObserver.java b/lifecycle/common/src/main/java/androidx/lifecycle/FullLifecycleObserver.java
new file mode 100644
index 0000000..c959d03
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/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 androidx.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/lifecycle/common/src/main/java/androidx/lifecycle/FullLifecycleObserverAdapter.java b/lifecycle/common/src/main/java/androidx/lifecycle/FullLifecycleObserverAdapter.java
new file mode 100644
index 0000000..4047173
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/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 androidx.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/lifecycle/common/src/main/java/androidx/lifecycle/GeneratedAdapter.java b/lifecycle/common/src/main/java/androidx/lifecycle/GeneratedAdapter.java
new file mode 100644
index 0000000..a70d67b
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/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 androidx.lifecycle;
+
+import androidx.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/lifecycle/common/src/main/java/androidx/lifecycle/GenericLifecycleObserver.java b/lifecycle/common/src/main/java/androidx/lifecycle/GenericLifecycleObserver.java
new file mode 100644
index 0000000..ffdd2c0
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/GenericLifecycleObserver.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Internal class that can receive any lifecycle change and dispatch it to the receiver.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressWarnings({"WeakerAccess", "unused"})
+public interface GenericLifecycleObserver extends LifecycleObserver {
+    /**
+     * Called when a state transition event happens.
+     *
+     * @param source The source of the event
+     * @param event The event
+     */
+    void onStateChanged(LifecycleOwner source, Lifecycle.Event event);
+}
diff --git a/lifecycle/common/src/main/java/androidx/lifecycle/Lifecycle.java b/lifecycle/common/src/main/java/androidx/lifecycle/Lifecycle.java
new file mode 100644
index 0000000..9b6a5dc
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/Lifecycle.java
@@ -0,0 +1,202 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+
+/**
+ * Defines an object that has an Android Lifecycle. {@link androidx.fragment.app.Fragment Fragment}
+ * and {@link androidx.fragment.app.FragmentActivity FragmentActivity} classes implement
+ * {@link LifecycleOwner} interface which has the {@link LifecycleOwner#getLifecycle()
+ * getLifecycle} method to access the Lifecycle. You can also implement {@link LifecycleOwner}
+ * in your own classes.
+ * <p>
+ * {@link Event#ON_CREATE}, {@link Event#ON_START}, {@link Event#ON_RESUME} events in this class
+ * are dispatched <b>after</b> the {@link LifecycleOwner}'s related method returns.
+ * {@link Event#ON_PAUSE}, {@link Event#ON_STOP}, {@link Event#ON_DESTROY} events in this class
+ * are dispatched <b>before</b> the {@link LifecycleOwner}'s related method is called.
+ * For instance, {@link Event#ON_START} will be dispatched after
+ * {@link android.app.Activity#onStart onStart} returns, {@link Event#ON_STOP} will be dispatched
+ * before {@link android.app.Activity#onStop onStop} is called.
+ * This gives you certain guarantees on which state the owner is in.
+ * <p>
+ * If you use <b>Java 8 Language</b>, then observe events with {@link DefaultLifecycleObserver}.
+ * To include it you should add {@code "androidx.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)
+ *   void onStopped() {}
+ * }
+ * </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
+ * of type {@link Event}.
+ * <pre>
+ * class TestObserver implements LifecycleObserver {
+ *   {@literal @}OnLifecycleEvent(ON_CREATE)
+ *   void onCreated(LifecycleOwner source) {}
+ *   {@literal @}OnLifecycleEvent(ON_ANY)
+ *   void onAny(LifecycleOwner source, Event event) {}
+ * }
+ * </pre>
+ * These additional parameters are provided to allow you to conveniently observe multiple providers
+ * and events without tracking them manually.
+ */
+public abstract class Lifecycle {
+    /**
+     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
+     * state.
+     * <p>
+     * The given observer will be brought to the current state of the LifecycleOwner.
+     * For example, if the LifecycleOwner is in {@link State#STARTED} state, the given observer
+     * will receive {@link Event#ON_CREATE}, {@link Event#ON_START} events.
+     *
+     * @param observer The observer to notify.
+     */
+    @MainThread
+    public abstract void addObserver(@NonNull LifecycleObserver observer);
+
+    /**
+     * Removes the given observer from the observers list.
+     * <p>
+     * If this method is called while a state change is being dispatched,
+     * <ul>
+     * <li>If the given observer has not yet received that event, it will not receive it.
+     * <li>If the given observer has more than 1 method that observes the currently dispatched
+     * event and at least one of them received the event, all of them will receive the event and
+     * the removal will happen afterwards.
+     * </ul>
+     *
+     * @param observer The observer to be removed.
+     */
+    @MainThread
+    public abstract void removeObserver(@NonNull LifecycleObserver observer);
+
+    /**
+     * Returns the current state of the Lifecycle.
+     *
+     * @return The current state of the Lifecycle.
+     */
+    @MainThread
+    @NonNull
+    public abstract State getCurrentState();
+
+    @SuppressWarnings("WeakerAccess")
+    public enum Event {
+        /**
+         * Constant for onCreate event of the {@link LifecycleOwner}.
+         */
+        ON_CREATE,
+        /**
+         * Constant for onStart event of the {@link LifecycleOwner}.
+         */
+        ON_START,
+        /**
+         * Constant for onResume event of the {@link LifecycleOwner}.
+         */
+        ON_RESUME,
+        /**
+         * Constant for onPause event of the {@link LifecycleOwner}.
+         */
+        ON_PAUSE,
+        /**
+         * Constant for onStop event of the {@link LifecycleOwner}.
+         */
+        ON_STOP,
+        /**
+         * Constant for onDestroy event of the {@link LifecycleOwner}.
+         */
+        ON_DESTROY,
+        /**
+         * An {@link Event Event} constant that can be used to match all events.
+         */
+        ON_ANY
+    }
+
+    /**
+     * Lifecycle states. You can consider the states as the nodes in a graph and
+     * {@link Event}s as the edges between these nodes.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public enum State {
+        /**
+         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
+         * any more events. For instance, for an {@link android.app.Activity}, this state is reached
+         * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
+         */
+        DESTROYED,
+
+        /**
+         * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
+         * the state when it is constructed but has not received
+         * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
+         */
+        INITIALIZED,
+
+        /**
+         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
+         * is reached in two cases:
+         * <ul>
+         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
+         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
+         * </ul>
+         */
+        CREATED,
+
+        /**
+         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
+         * is reached in two cases:
+         * <ul>
+         *     <li>after {@link android.app.Activity#onStart() onStart} call;
+         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
+         * </ul>
+         */
+        STARTED,
+
+        /**
+         * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
+         * is reached after {@link android.app.Activity#onResume() onResume} is called.
+         */
+        RESUMED;
+
+        /**
+         * Compares if this State is greater or equal to the given {@code state}.
+         *
+         * @param state State to compare with
+         * @return true if this State is greater or equal to the given {@code state}
+         */
+        public boolean isAtLeast(@NonNull State state) {
+            return compareTo(state) >= 0;
+        }
+    }
+}
diff --git a/lifecycle/common/src/main/java/androidx/lifecycle/LifecycleObserver.java b/lifecycle/common/src/main/java/androidx/lifecycle/LifecycleObserver.java
new file mode 100644
index 0000000..f02aad8
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/LifecycleObserver.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 androidx.lifecycle;
+
+/**
+ * Marks a class as a LifecycleObserver. It does not have any methods, instead, relies on
+ * {@link OnLifecycleEvent} annotated methods.
+ * <p>
+ * @see Lifecycle Lifecycle - for samples and usage patterns.
+ */
+@SuppressWarnings("WeakerAccess")
+public interface LifecycleObserver {
+
+}
diff --git a/lifecycle/common/src/main/java/androidx/lifecycle/LifecycleOwner.java b/lifecycle/common/src/main/java/androidx/lifecycle/LifecycleOwner.java
new file mode 100644
index 0000000..2ddffc2
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/LifecycleOwner.java
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A class that has an Android lifecycle. These events can be used by custom components to
+ * handle lifecycle changes without implementing any code inside the Activity or the Fragment.
+ *
+ * @see Lifecycle
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public interface LifecycleOwner {
+    /**
+     * Returns the Lifecycle of the provider.
+     *
+     * @return The lifecycle of the provider.
+     */
+    @NonNull
+    Lifecycle getLifecycle();
+}
diff --git a/lifecycle/common/src/main/java/androidx/lifecycle/Lifecycling.java b/lifecycle/common/src/main/java/androidx/lifecycle/Lifecycling.java
new file mode 100644
index 0000000..47c66c8
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/Lifecycling.java
@@ -0,0 +1,181 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+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)
+public class Lifecycling {
+
+    private static final int REFLECTIVE_CALLBACK = 1;
+    private static final int GENERATED_CALLBACK = 2;
+
+    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 {
+            return constructor.newInstance(object);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    private static Constructor<? extends GeneratedAdapter> generatedConstructor(Class<?> klass) {
+        try {
+            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);
+            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);
+        }
+    }
+
+    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/lifecycle/common/src/main/java/androidx/lifecycle/MethodCallsLogger.java b/lifecycle/common/src/main/java/androidx/lifecycle/MethodCallsLogger.java
new file mode 100644
index 0000000..0401a22
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/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 androidx.lifecycle;
+
+import androidx.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/lifecycle/common/src/main/java/androidx/lifecycle/OnLifecycleEvent.java b/lifecycle/common/src/main/java/androidx/lifecycle/OnLifecycleEvent.java
new file mode 100644
index 0000000..cfb72a0
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/OnLifecycleEvent.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 androidx.lifecycle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@SuppressWarnings("unused")
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnLifecycleEvent {
+    Lifecycle.Event value();
+}
diff --git a/lifecycle/common/src/main/java/androidx/lifecycle/ReflectiveGenericLifecycleObserver.java b/lifecycle/common/src/main/java/androidx/lifecycle/ReflectiveGenericLifecycleObserver.java
new file mode 100644
index 0000000..e94f02e
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/lifecycle/ReflectiveGenericLifecycleObserver.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 androidx.lifecycle;
+
+import androidx.lifecycle.ClassesInfoCache.CallbackInfo;
+import androidx.lifecycle.Lifecycle.Event;
+
+/**
+ * An internal implementation of {@link GenericLifecycleObserver} that relies on reflection.
+ */
+class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
+    private final Object mWrapped;
+    private final CallbackInfo mInfo;
+
+    ReflectiveGenericLifecycleObserver(Object wrapped) {
+        mWrapped = wrapped;
+        mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass());
+    }
+
+    @Override
+    public void onStateChanged(LifecycleOwner source, Event event) {
+        mInfo.invokeCallbacks(source, event, mWrapped);
+    }
+}
diff --git a/lifecycle/common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.java b/lifecycle/common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.java
new file mode 100644
index 0000000..dc4d159
--- /dev/null
+++ b/lifecycle/common/src/main/java/androidx/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 androidx.lifecycle;
+
+import androidx.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/lifecycle/common/src/test/java/android/arch/lifecycle/FullLifecycleObserverTest.java b/lifecycle/common/src/test/java/android/arch/lifecycle/FullLifecycleObserverTest.java
deleted file mode 100644
index def6755..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/FullLifecycleObserverTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.Lifecycle.State.CREATED;
-import 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/lifecycle/common/src/test/java/android/arch/lifecycle/LifecyclingTest.java b/lifecycle/common/src/test/java/android/arch/lifecycle/LifecyclingTest.java
deleted file mode 100644
index 70ce84c..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/LifecyclingTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.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/lifecycle/common/src/test/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserverTest.java b/lifecycle/common/src/test/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserverTest.java
deleted file mode 100644
index 23aed28..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/ReflectiveGenericLifecycleObserverTest.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
-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.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Matchers;
-
-@RunWith(JUnit4.class)
-public class ReflectiveGenericLifecycleObserverTest {
-    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 anyState() {
-        AnyStateListener obj = mock(AnyStateListener.class);
-        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
-        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
-        observer.onStateChanged(mOwner, ON_CREATE);
-        verify(obj).onAnyState(mOwner, ON_CREATE);
-        reset(obj);
-
-        observer.onStateChanged(mOwner, ON_START);
-        verify(obj).onAnyState(mOwner, ON_START);
-        reset(obj);
-
-        observer.onStateChanged(mOwner, ON_RESUME);
-        verify(obj).onAnyState(mOwner, ON_RESUME);
-        reset(obj);
-
-        observer.onStateChanged(mOwner, ON_PAUSE);
-        verify(obj).onAnyState(mOwner, ON_PAUSE);
-        reset(obj);
-
-        observer.onStateChanged(mOwner, ON_STOP);
-        verify(obj).onAnyState(mOwner, ON_STOP);
-        reset(obj);
-
-        observer.onStateChanged(mOwner, ON_DESTROY);
-        verify(obj).onAnyState(mOwner, ON_DESTROY);
-        reset(obj);
-    }
-
-    private static class AnyStateListener implements LifecycleObserver {
-        @OnLifecycleEvent(ON_ANY)
-        void onAnyState(LifecycleOwner owner, Lifecycle.Event event) {
-
-        }
-    }
-
-    @Test
-    public void singleMethod() {
-        CreatedStateListener obj = mock(CreatedStateListener.class);
-        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
-        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
-        observer.onStateChanged(mOwner, ON_CREATE);
-        verify(obj).onCreated();
-        verify(obj).onCreated(mOwner);
-    }
-
-    private static class CreatedStateListener implements LifecycleObserver {
-        @OnLifecycleEvent(ON_CREATE)
-        void onCreated() {
-
-        }
-        @SuppressWarnings("UnusedParameters")
-        @OnLifecycleEvent(ON_CREATE)
-        void onCreated(LifecycleOwner provider) {
-
-        }
-    }
-
-    @Test
-    public void eachEvent() {
-        AllMethodsListener obj = mock(AllMethodsListener.class);
-        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
-        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
-
-        observer.onStateChanged(mOwner, ON_CREATE);
-        verify(obj).created();
-        reset(obj);
-
-        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
-        observer.onStateChanged(mOwner, ON_START);
-        verify(obj).started();
-        reset(obj);
-
-        when(mLifecycle.getCurrentState()).thenReturn(RESUMED);
-        observer.onStateChanged(mOwner, ON_RESUME);
-        verify(obj).resumed();
-        reset(obj);
-
-        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
-        observer.onStateChanged(mOwner, ON_PAUSE);
-        verify(obj).paused();
-        reset(obj);
-
-        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
-        observer.onStateChanged(mOwner, ON_STOP);
-        verify(obj).stopped();
-        reset(obj);
-
-        when(mLifecycle.getCurrentState()).thenReturn(INITIALIZED);
-        observer.onStateChanged(mOwner, ON_DESTROY);
-        verify(obj).destroyed();
-        reset(obj);
-    }
-
-
-    private static class AllMethodsListener implements LifecycleObserver {
-        @OnLifecycleEvent(ON_CREATE)
-        void created() {}
-
-        @OnLifecycleEvent(ON_START)
-        void started() {}
-
-        @OnLifecycleEvent(ON_RESUME)
-        void resumed() {}
-
-        @OnLifecycleEvent(ON_PAUSE)
-        void paused() {}
-
-        @OnLifecycleEvent(ON_STOP)
-        void stopped() {}
-
-        @OnLifecycleEvent(ON_DESTROY)
-        void destroyed() {
-        }
-    }
-
-    @Test
-    public void testFailingObserver() {
-        class UnprecedentedError extends Error {
-        }
-
-        LifecycleObserver obj = new LifecycleObserver() {
-            @OnLifecycleEvent(ON_START)
-            void started() {
-                throw new UnprecedentedError();
-            }
-        };
-        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
-        try {
-            observer.onStateChanged(mOwner, ON_START);
-            fail();
-        } catch (Exception e) {
-            assertThat("exception cause is wrong",
-                    e.getCause() instanceof UnprecedentedError);
-        }
-    }
-
-    @Test
-    public void testPrivateObserverMethods() {
-        class ObserverWithPrivateMethod implements LifecycleObserver {
-            boolean mCalled = false;
-            @OnLifecycleEvent(ON_START)
-            private void started() {
-                mCalled = true;
-            }
-        }
-
-        ObserverWithPrivateMethod obj = mock(ObserverWithPrivateMethod.class);
-        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
-        observer.onStateChanged(mOwner, ON_START);
-        assertThat(obj.mCalled, is(true));
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testWrongFirstParam1() {
-        LifecycleObserver observer = new LifecycleObserver() {
-            @OnLifecycleEvent(ON_START)
-            private void started(Lifecycle.Event e) {
-            }
-        };
-        new ReflectiveGenericLifecycleObserver(observer);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testWrongFirstParam2() {
-        LifecycleObserver observer = new LifecycleObserver() {
-            @OnLifecycleEvent(ON_ANY)
-            private void started(Lifecycle l, Lifecycle.Event e) {
-            }
-        };
-        new ReflectiveGenericLifecycleObserver(observer);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testWrongSecondParam() {
-        LifecycleObserver observer = new LifecycleObserver() {
-            @OnLifecycleEvent(ON_START)
-            private void started(LifecycleOwner owner, Lifecycle l) {
-            }
-        };
-        new ReflectiveGenericLifecycleObserver(observer);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testThreeParams() {
-        LifecycleObserver observer = new LifecycleObserver() {
-            @OnLifecycleEvent(ON_ANY)
-            private void started(LifecycleOwner owner, Lifecycle.Event e, int i) {
-            }
-        };
-        new ReflectiveGenericLifecycleObserver(observer);
-    }
-
-    static class BaseClass1 implements LifecycleObserver {
-        @OnLifecycleEvent(ON_START)
-        void foo(LifecycleOwner owner) {
-        }
-    }
-
-    static class DerivedClass1 extends BaseClass1 {
-        @Override
-        @OnLifecycleEvent(ON_STOP)
-        void foo(LifecycleOwner owner) {
-        }
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testInvalidSuper1() {
-        new ReflectiveGenericLifecycleObserver(new DerivedClass1());
-    }
-
-    static class BaseClass2 implements LifecycleObserver {
-        @OnLifecycleEvent(ON_START)
-        void foo(LifecycleOwner owner) {
-        }
-    }
-
-    static class DerivedClass2 extends BaseClass1 {
-        @OnLifecycleEvent(ON_STOP)
-        void foo() {
-        }
-    }
-
-    @Test
-    public void testValidSuper1() {
-        DerivedClass2 obj = mock(DerivedClass2.class);
-        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
-        observer.onStateChanged(mock(LifecycleOwner.class), ON_START);
-        verify(obj).foo(Matchers.<LifecycleOwner>any());
-        verify(obj, never()).foo();
-        reset(obj);
-        observer.onStateChanged(mock(LifecycleOwner.class), ON_STOP);
-        verify(obj).foo();
-        verify(obj, never()).foo(Matchers.<LifecycleOwner>any());
-    }
-
-    static class BaseClass3 implements LifecycleObserver {
-        @OnLifecycleEvent(ON_START)
-        void foo(LifecycleOwner owner) {
-        }
-    }
-
-    interface Interface3 extends LifecycleObserver {
-        @OnLifecycleEvent(ON_STOP)
-        void foo(LifecycleOwner owner);
-    }
-
-    static class DerivedClass3 extends BaseClass3 implements Interface3 {
-        @Override
-        public void foo(LifecycleOwner owner) {
-        }
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testInvalidSuper2() {
-        new ReflectiveGenericLifecycleObserver(new DerivedClass3());
-    }
-
-    static class BaseClass4 implements LifecycleObserver {
-        @OnLifecycleEvent(ON_START)
-        void foo(LifecycleOwner owner) {
-        }
-    }
-
-    interface Interface4 extends LifecycleObserver {
-        @OnLifecycleEvent(ON_START)
-        void foo(LifecycleOwner owner);
-    }
-
-    static class DerivedClass4 extends BaseClass4 implements Interface4 {
-        @Override
-        @OnLifecycleEvent(ON_START)
-        public void foo(LifecycleOwner owner) {
-        }
-
-        @OnLifecycleEvent(ON_START)
-        public void foo() {
-        }
-    }
-
-    @Test
-    public void testValidSuper2() {
-        DerivedClass4 obj = mock(DerivedClass4.class);
-        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
-        observer.onStateChanged(mock(LifecycleOwner.class), ON_START);
-        verify(obj).foo(Matchers.<LifecycleOwner>any());
-        verify(obj).foo();
-    }
-
-    interface InterfaceStart extends LifecycleObserver {
-        @OnLifecycleEvent(ON_START)
-        void foo(LifecycleOwner owner);
-    }
-
-    interface InterfaceStop extends LifecycleObserver {
-        @OnLifecycleEvent(ON_STOP)
-        void foo(LifecycleOwner owner);
-    }
-
-    static class DerivedClass5 implements InterfaceStart, InterfaceStop {
-        @Override
-        public void foo(LifecycleOwner owner) {
-        }
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testInvalidSuper3() {
-        new ReflectiveGenericLifecycleObserver(new DerivedClass5());
-    }
-}
diff --git a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Base.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Base.java
deleted file mode 100644
index 08919d4..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Base.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Base_LifecycleAdapter.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
deleted file mode 100644
index 4218b96..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedSequence1.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedSequence1.java
deleted file mode 100644
index 9db37f1..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedSequence1.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.observers;
-
-public class DerivedSequence1 extends Base {
-
-    public void something() {
-    }
-}
diff --git a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedSequence2.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedSequence2.java
deleted file mode 100644
index f2ef943..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedSequence2.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithNewMethods.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithNewMethods.java
deleted file mode 100644
index b1eaef0..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithNewMethods.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
deleted file mode 100644
index cb1afb8..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.observers;
-
-public class DerivedWithNoNewMethods extends Base {
-}
diff --git a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
deleted file mode 100644
index 40c7c9a..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface1.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface1.java
deleted file mode 100644
index e193de9..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface1.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
deleted file mode 100644
index c597b1c..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface2.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface2.java
deleted file mode 100644
index 1056fcb..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface2.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
deleted file mode 100644
index b05b41a..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl1.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl1.java
deleted file mode 100644
index 2f03393..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl1.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.observers;
-
-public class InterfaceImpl1 implements Interface1 {
-    @Override
-    public void onCreate() {
-    }
-}
diff --git a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl2.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl2.java
deleted file mode 100644
index eef8ce4..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl2.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.observers;
-
-public class InterfaceImpl2 implements Interface1, Interface2 {
-    @Override
-    public void onCreate() {
-    }
-
-    @Override
-    public void onDestroy() {
-
-    }
-}
diff --git a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl3.java b/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl3.java
deleted file mode 100644
index 8f31808..0000000
--- a/lifecycle/common/src/test/java/android/arch/lifecycle/observers/InterfaceImpl3.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.observers;
-
-public class InterfaceImpl3 extends Base implements Interface1 {
-    @Override
-    public void onCreate() {
-    }
-}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/FullLifecycleObserverTest.java b/lifecycle/common/src/test/java/androidx/lifecycle/FullLifecycleObserverTest.java
new file mode 100644
index 0000000..79dd8f4
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.State.CREATED;
+import static androidx.lifecycle.Lifecycle.State.INITIALIZED;
+import static androidx.lifecycle.Lifecycle.State.RESUMED;
+import static androidx.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/lifecycle/common/src/test/java/androidx/lifecycle/LifecyclingTest.java b/lifecycle/common/src/test/java/androidx/lifecycle/LifecyclingTest.java
new file mode 100644
index 0000000..9cc251e
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import androidx.lifecycle.observers.DerivedSequence1;
+import androidx.lifecycle.observers.DerivedSequence2;
+import androidx.lifecycle.observers.DerivedWithNewMethods;
+import androidx.lifecycle.observers.DerivedWithNoNewMethods;
+import androidx.lifecycle.observers.DerivedWithOverridenMethodsWithLfAnnotation;
+import androidx.lifecycle.observers.InterfaceImpl1;
+import androidx.lifecycle.observers.InterfaceImpl2;
+import androidx.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/lifecycle/common/src/test/java/androidx/lifecycle/ReflectiveGenericLifecycleObserverTest.java b/lifecycle/common/src/test/java/androidx/lifecycle/ReflectiveGenericLifecycleObserverTest.java
new file mode 100644
index 0000000..4966500
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/lifecycle/ReflectiveGenericLifecycleObserverTest.java
@@ -0,0 +1,368 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_ANY;
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.State.CREATED;
+import static androidx.lifecycle.Lifecycle.State.INITIALIZED;
+import static androidx.lifecycle.Lifecycle.State.RESUMED;
+import static androidx.lifecycle.Lifecycle.State.STARTED;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Matchers;
+
+@RunWith(JUnit4.class)
+public class ReflectiveGenericLifecycleObserverTest {
+    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 anyState() {
+        AnyStateListener obj = mock(AnyStateListener.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_CREATE);
+        verify(obj).onAnyState(mOwner, ON_CREATE);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_START);
+        verify(obj).onAnyState(mOwner, ON_START);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_RESUME);
+        verify(obj).onAnyState(mOwner, ON_RESUME);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_PAUSE);
+        verify(obj).onAnyState(mOwner, ON_PAUSE);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_STOP);
+        verify(obj).onAnyState(mOwner, ON_STOP);
+        reset(obj);
+
+        observer.onStateChanged(mOwner, ON_DESTROY);
+        verify(obj).onAnyState(mOwner, ON_DESTROY);
+        reset(obj);
+    }
+
+    private static class AnyStateListener implements LifecycleObserver {
+        @OnLifecycleEvent(ON_ANY)
+        void onAnyState(LifecycleOwner owner, Lifecycle.Event event) {
+
+        }
+    }
+
+    @Test
+    public void singleMethod() {
+        CreatedStateListener obj = mock(CreatedStateListener.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+        observer.onStateChanged(mOwner, ON_CREATE);
+        verify(obj).onCreated();
+        verify(obj).onCreated(mOwner);
+    }
+
+    private static class CreatedStateListener implements LifecycleObserver {
+        @OnLifecycleEvent(ON_CREATE)
+        void onCreated() {
+
+        }
+        @SuppressWarnings("UnusedParameters")
+        @OnLifecycleEvent(ON_CREATE)
+        void onCreated(LifecycleOwner provider) {
+
+        }
+    }
+
+    @Test
+    public void eachEvent() {
+        AllMethodsListener obj = mock(AllMethodsListener.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+
+        observer.onStateChanged(mOwner, ON_CREATE);
+        verify(obj).created();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_START);
+        verify(obj).started();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(RESUMED);
+        observer.onStateChanged(mOwner, ON_RESUME);
+        verify(obj).resumed();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+        observer.onStateChanged(mOwner, ON_PAUSE);
+        verify(obj).paused();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+        observer.onStateChanged(mOwner, ON_STOP);
+        verify(obj).stopped();
+        reset(obj);
+
+        when(mLifecycle.getCurrentState()).thenReturn(INITIALIZED);
+        observer.onStateChanged(mOwner, ON_DESTROY);
+        verify(obj).destroyed();
+        reset(obj);
+    }
+
+
+    private static class AllMethodsListener implements LifecycleObserver {
+        @OnLifecycleEvent(ON_CREATE)
+        void created() {}
+
+        @OnLifecycleEvent(ON_START)
+        void started() {}
+
+        @OnLifecycleEvent(ON_RESUME)
+        void resumed() {}
+
+        @OnLifecycleEvent(ON_PAUSE)
+        void paused() {}
+
+        @OnLifecycleEvent(ON_STOP)
+        void stopped() {}
+
+        @OnLifecycleEvent(ON_DESTROY)
+        void destroyed() {
+        }
+    }
+
+    @Test
+    public void testFailingObserver() {
+        class UnprecedentedError extends Error {
+        }
+
+        LifecycleObserver obj = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_START)
+            void started() {
+                throw new UnprecedentedError();
+            }
+        };
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        try {
+            observer.onStateChanged(mOwner, ON_START);
+            fail();
+        } catch (Exception e) {
+            assertThat("exception cause is wrong",
+                    e.getCause() instanceof UnprecedentedError);
+        }
+    }
+
+    @Test
+    public void testPrivateObserverMethods() {
+        class ObserverWithPrivateMethod implements LifecycleObserver {
+            boolean mCalled = false;
+            @OnLifecycleEvent(ON_START)
+            private void started() {
+                mCalled = true;
+            }
+        }
+
+        ObserverWithPrivateMethod obj = mock(ObserverWithPrivateMethod.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        observer.onStateChanged(mOwner, ON_START);
+        assertThat(obj.mCalled, is(true));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrongFirstParam1() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_START)
+            private void started(Lifecycle.Event e) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrongFirstParam2() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_ANY)
+            private void started(Lifecycle l, Lifecycle.Event e) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWrongSecondParam() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_START)
+            private void started(LifecycleOwner owner, Lifecycle l) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testThreeParams() {
+        LifecycleObserver observer = new LifecycleObserver() {
+            @OnLifecycleEvent(ON_ANY)
+            private void started(LifecycleOwner owner, Lifecycle.Event e, int i) {
+            }
+        };
+        new ReflectiveGenericLifecycleObserver(observer);
+    }
+
+    static class BaseClass1 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    static class DerivedClass1 extends BaseClass1 {
+        @Override
+        @OnLifecycleEvent(ON_STOP)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidSuper1() {
+        new ReflectiveGenericLifecycleObserver(new DerivedClass1());
+    }
+
+    static class BaseClass2 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    static class DerivedClass2 extends BaseClass1 {
+        @OnLifecycleEvent(ON_STOP)
+        void foo() {
+        }
+    }
+
+    @Test
+    public void testValidSuper1() {
+        DerivedClass2 obj = mock(DerivedClass2.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        observer.onStateChanged(mock(LifecycleOwner.class), ON_START);
+        verify(obj).foo(Matchers.<LifecycleOwner>any());
+        verify(obj, never()).foo();
+        reset(obj);
+        observer.onStateChanged(mock(LifecycleOwner.class), ON_STOP);
+        verify(obj).foo();
+        verify(obj, never()).foo(Matchers.<LifecycleOwner>any());
+    }
+
+    static class BaseClass3 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    interface Interface3 extends LifecycleObserver {
+        @OnLifecycleEvent(ON_STOP)
+        void foo(LifecycleOwner owner);
+    }
+
+    static class DerivedClass3 extends BaseClass3 implements Interface3 {
+        @Override
+        public void foo(LifecycleOwner owner) {
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidSuper2() {
+        new ReflectiveGenericLifecycleObserver(new DerivedClass3());
+    }
+
+    static class BaseClass4 implements LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner) {
+        }
+    }
+
+    interface Interface4 extends LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner);
+    }
+
+    static class DerivedClass4 extends BaseClass4 implements Interface4 {
+        @Override
+        @OnLifecycleEvent(ON_START)
+        public void foo(LifecycleOwner owner) {
+        }
+
+        @OnLifecycleEvent(ON_START)
+        public void foo() {
+        }
+    }
+
+    @Test
+    public void testValidSuper2() {
+        DerivedClass4 obj = mock(DerivedClass4.class);
+        ReflectiveGenericLifecycleObserver observer = new ReflectiveGenericLifecycleObserver(obj);
+        observer.onStateChanged(mock(LifecycleOwner.class), ON_START);
+        verify(obj).foo(Matchers.<LifecycleOwner>any());
+        verify(obj).foo();
+    }
+
+    interface InterfaceStart extends LifecycleObserver {
+        @OnLifecycleEvent(ON_START)
+        void foo(LifecycleOwner owner);
+    }
+
+    interface InterfaceStop extends LifecycleObserver {
+        @OnLifecycleEvent(ON_STOP)
+        void foo(LifecycleOwner owner);
+    }
+
+    static class DerivedClass5 implements InterfaceStart, InterfaceStop {
+        @Override
+        public void foo(LifecycleOwner owner) {
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidSuper3() {
+        new ReflectiveGenericLifecycleObserver(new DerivedClass5());
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/Base.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/Base.java
new file mode 100644
index 0000000..39649eb
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+
+public class Base implements LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    public void onCreate() {
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/Base_LifecycleAdapter.java
new file mode 100644
index 0000000..155825c
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.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/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedSequence1.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedSequence1.java
new file mode 100644
index 0000000..ae9c85f
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+public class DerivedSequence1 extends Base {
+
+    public void something() {
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedSequence2.java
new file mode 100644
index 0000000..38b66c5
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.OnLifecycleEvent;
+
+public class DerivedSequence2 extends DerivedSequence1 {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    void onStop() {
+
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedWithNewMethods.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedWithNewMethods.java
new file mode 100644
index 0000000..dc5dc13
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithNewMethods extends Base {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    void onStop() {
+
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedWithNoNewMethods.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedWithNoNewMethods.java
new file mode 100644
index 0000000..d8bfeea
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+public class DerivedWithNoNewMethods extends Base {
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
new file mode 100644
index 0000000..d1cec8b
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithOverridenMethodsWithLfAnnotation extends Base {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface1.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface1.java
new file mode 100644
index 0000000..f6f4960
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+
+public interface Interface1 extends LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    void onCreate();
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface1_LifecycleAdapter.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface1_LifecycleAdapter.java
new file mode 100644
index 0000000..5dc2fd2
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.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/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface2.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface2.java
new file mode 100644
index 0000000..080d43d
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+
+public interface Interface2 extends LifecycleObserver {
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    void onCreate();
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    void onDestroy();
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface2_LifecycleAdapter.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/Interface2_LifecycleAdapter.java
new file mode 100644
index 0000000..9c02c43
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.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/lifecycle/common/src/test/java/androidx/lifecycle/observers/InterfaceImpl1.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/InterfaceImpl1.java
new file mode 100644
index 0000000..0b07605
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+public class InterfaceImpl1 implements Interface1 {
+    @Override
+    public void onCreate() {
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/InterfaceImpl2.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/InterfaceImpl2.java
new file mode 100644
index 0000000..2f5d9d9
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+public class InterfaceImpl2 implements Interface1, Interface2 {
+    @Override
+    public void onCreate() {
+    }
+
+    @Override
+    public void onDestroy() {
+
+    }
+}
diff --git a/lifecycle/common/src/test/java/androidx/lifecycle/observers/InterfaceImpl3.java b/lifecycle/common/src/test/java/androidx/lifecycle/observers/InterfaceImpl3.java
new file mode 100644
index 0000000..e6d8a63
--- /dev/null
+++ b/lifecycle/common/src/test/java/androidx/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 androidx.lifecycle.observers;
+
+public class InterfaceImpl3 extends Base implements Interface1 {
+    @Override
+    public void onCreate() {
+    }
+}
diff --git a/lifecycle/compiler/build.gradle b/lifecycle/compiler/build.gradle
index fec3d99..90df541 100644
--- a/lifecycle/compiler/build.gradle
+++ b/lifecycle/compiler/build.gradle
@@ -19,7 +19,7 @@
 }
 
 dependencies {
-    compile(project(":lifecycle:common"))
+    compile(project(":lifecycle:lifecycle-common"))
     compile(KOTLIN_STDLIB)
     compile(AUTO_COMMON)
     compile(JAVAPOET)
@@ -28,7 +28,7 @@
     testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
 }
 
-// we actually need to compile :lifecycle:common, but compileJava is easier
+// we actually need to compile :lifecycle:lifecycle-common, but compileJava is easier
 task compileTestLibrarySource(type: JavaCompile, dependsOn: compileJava) {
     source "src/tests/test-data/lib/src"
     classpath = project.compileJava.classpath
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
deleted file mode 100644
index 2f300e7..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import android.arch.lifecycle.model.EventMethod
-import javax.lang.model.element.TypeElement
-
-object ErrorMessages {
-    const val TOO_MANY_ARGS = "callback method cannot have more than 2 parameters"
-    const val TOO_MANY_ARGS_NOT_ON_ANY = "only callback annotated with ON_ANY " +
-            "can have 2 parameters"
-    const val INVALID_SECOND_ARGUMENT = "2nd argument of a callback method" +
-            " must be Lifecycle.Event and represent the current event"
-    const val INVALID_FIRST_ARGUMENT = "1st argument of a callback method must be " +
-            "a LifecycleOwner which represents the source of the event"
-    const val INVALID_METHOD_MODIFIER = "method marked with OnLifecycleEvent annotation can " +
-            "not be private"
-    const val INVALID_CLASS_MODIFIER = "class containing OnLifecycleEvent methods can not be " +
-            "private"
-    const val INVALID_STATE_OVERRIDE_METHOD = "overridden method must handle the same " +
-            "onState changes as original method"
-    const val INVALID_ENCLOSING_ELEMENT =
-            "Parent of OnLifecycleEvent should be a class or interface"
-    const val INVALID_ANNOTATED_ELEMENT = "OnLifecycleEvent can only be added to methods"
-
-    fun failedToGenerateAdapter(type: TypeElement, failureReason: EventMethod) =
-            """
-             Failed to generate an Adapter for $type, because it needs to be able to access to
-             package private method ${failureReason.method.name()} from ${failureReason.type}
-            """.trim()
-}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt
deleted file mode 100644
index f57d2dd..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/LifecycleProcessor.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import javax.annotation.processing.AbstractProcessor
-import javax.annotation.processing.RoundEnvironment
-import javax.annotation.processing.SupportedAnnotationTypes
-import javax.lang.model.SourceVersion
-import javax.lang.model.element.TypeElement
-
-@SupportedAnnotationTypes("android.arch.lifecycle.OnLifecycleEvent")
-class LifecycleProcessor : AbstractProcessor() {
-    override fun process(annotations: MutableSet<out TypeElement>,
-                         roundEnv: RoundEnvironment): Boolean {
-        val input = collectAndVerifyInput(processingEnv, roundEnv)
-        writeModels(transformToOutput(processingEnv, input), processingEnv)
-        return true
-    }
-
-    override fun getSupportedSourceVersion(): SourceVersion {
-        return SourceVersion.latest()
-    }
-}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/elements_ext.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/elements_ext.kt
deleted file mode 100644
index 08b6ff2..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/elements_ext.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import com.google.auto.common.MoreElements
-import javax.lang.model.element.Element
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.PackageElement
-import javax.lang.model.element.TypeElement
-import javax.lang.model.util.ElementFilter
-
-fun Element.getPackage(): PackageElement = MoreElements.getPackage(this)
-
-fun Element.getPackageQName() = getPackage().qualifiedName.toString()
-
-fun ExecutableElement.name() = simpleName.toString()
-
-fun ExecutableElement.isPackagePrivate() = !modifiers.any {
-    it == Modifier.PUBLIC || it == Modifier.PROTECTED || it == Modifier.PRIVATE
-}
-
-fun ExecutableElement.isProtected() = modifiers.contains(Modifier.PROTECTED)
-
-fun TypeElement.methods(): List<ExecutableElement> = ElementFilter.methodsIn(enclosedElements)
-
-private const val SYNTHETIC = "__synthetic_"
-
-fun syntheticName(method: ExecutableElement) = "$SYNTHETIC${method.simpleName}"
-
-fun isSyntheticMethod(method: ExecutableElement) = method.name().startsWith(SYNTHETIC)
-
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
deleted file mode 100644
index 843ac4e..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import android.arch.lifecycle.model.EventMethod
-import android.arch.lifecycle.model.InputModel
-import android.arch.lifecycle.model.LifecycleObserverInfo
-import android.arch.lifecycle.model.getAdapterName
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import javax.annotation.processing.ProcessingEnvironment
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.TypeElement
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.TypeMirror
-import javax.lang.model.util.Elements
-import javax.lang.model.util.Types
-import javax.tools.Diagnostic
-
-fun collectAndVerifyInput(processingEnv: ProcessingEnvironment,
-                          roundEnv: RoundEnvironment): InputModel {
-    val validator = Validator(processingEnv)
-    val worldCollector = ObserversCollector(processingEnv)
-    val roots = roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).map { elem ->
-        if (elem.kind != ElementKind.METHOD) {
-            validator.printErrorMessage(ErrorMessages.INVALID_ANNOTATED_ELEMENT, elem)
-            null
-        } else {
-            val enclosingElement = elem.enclosingElement
-            if (validator.validateClass(enclosingElement)) {
-                MoreElements.asType(enclosingElement)
-            } else {
-                null
-            }
-        }
-    }.filterNotNull().toSet()
-    roots.forEach { worldCollector.collect(it) }
-    val observersInfo = worldCollector.observers
-    val generatedAdapters = worldCollector.observers.keys
-            .mapNotNull { type ->
-                worldCollector.generatedAdapterInfoFor(type)?.let { type to it }
-            }.toMap()
-    return InputModel(roots, observersInfo, generatedAdapters)
-}
-
-class ObserversCollector(processingEnv: ProcessingEnvironment) {
-    val typeUtils: Types = processingEnv.typeUtils
-    val elementUtils: Elements = processingEnv.elementUtils
-    val lifecycleObserverTypeMirror: TypeMirror =
-            elementUtils.getTypeElement(LifecycleObserver::class.java.canonicalName).asType()
-    val validator = Validator(processingEnv)
-    val observers: MutableMap<TypeElement, LifecycleObserverInfo> = mutableMapOf()
-
-    fun collect(type: TypeElement): LifecycleObserverInfo? {
-        if (type in observers) {
-            return observers[type]
-        }
-        val parents = (listOf(type.superclass) + type.interfaces)
-                .filter { typeUtils.isAssignable(it, lifecycleObserverTypeMirror) }
-                .filterNot { typeUtils.isSameType(it, lifecycleObserverTypeMirror) }
-                .map { collect(MoreTypes.asTypeElement(it)) }
-                .filterNotNull()
-        val info = createObserverInfo(type, parents)
-        if (info != null) {
-            observers[type] = info
-        }
-        return info
-    }
-
-    fun generatedAdapterInfoFor(type: TypeElement): List<ExecutableElement>? {
-        val packageName = if (type.getPackageQName().isEmpty()) "" else "${type.getPackageQName()}."
-        val adapterType = elementUtils.getTypeElement(packageName + getAdapterName(type))
-        return adapterType?.methods()
-                ?.filter { executable -> isSyntheticMethod(executable) }
-    }
-
-    private fun createObserverInfo(typeElement: TypeElement,
-                                   parents: List<LifecycleObserverInfo>): LifecycleObserverInfo? {
-        if (!validator.validateClass(typeElement)) {
-            return null
-        }
-        val methods = typeElement.methods().filter { executable ->
-            MoreElements.isAnnotationPresent(executable, OnLifecycleEvent::class.java)
-        }.map { executable ->
-            val onState = executable.getAnnotation(OnLifecycleEvent::class.java)
-            if (validator.validateMethod(executable, onState.value)) {
-                EventMethod(executable, onState, typeElement)
-            } else {
-                null
-            }
-        }.filterNotNull()
-        return LifecycleObserverInfo(typeElement, methods, parents)
-    }
-}
-
-class Validator(val processingEnv: ProcessingEnvironment) {
-
-    fun printErrorMessage(msg: CharSequence, elem: Element) {
-        processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, msg, elem)
-    }
-
-    fun validateParam(param: VariableElement,
-                      expectedType: Class<*>, errorMsg: String): Boolean {
-        if (!MoreTypes.isTypeOf(expectedType, param.asType())) {
-            printErrorMessage(errorMsg, param)
-            return false
-        }
-        return true
-    }
-
-    fun validateMethod(method: ExecutableElement, event: Lifecycle.Event): Boolean {
-        if (Modifier.PRIVATE in method.modifiers) {
-            printErrorMessage(ErrorMessages.INVALID_METHOD_MODIFIER, method)
-            return false
-        }
-        val params = method.parameters
-        if ((params.size > 2)) {
-            printErrorMessage(ErrorMessages.TOO_MANY_ARGS, method)
-            return false
-        }
-
-        if (params.size == 2 && event != Lifecycle.Event.ON_ANY) {
-            printErrorMessage(ErrorMessages.TOO_MANY_ARGS_NOT_ON_ANY, method)
-            return false
-        }
-
-        if (params.size == 2 && !validateParam(params[1], Lifecycle.Event::class.java,
-                ErrorMessages.INVALID_SECOND_ARGUMENT)) {
-            return false
-        }
-
-        if (params.size > 0) {
-            return validateParam(params[0], LifecycleOwner::class.java,
-                    ErrorMessages.INVALID_FIRST_ARGUMENT)
-        }
-        return true
-    }
-
-    fun validateClass(classElement: Element): Boolean {
-        if (!MoreElements.isType(classElement)) {
-            printErrorMessage(ErrorMessages.INVALID_ENCLOSING_ELEMENT, classElement)
-            return false
-        }
-        if (Modifier.PRIVATE in classElement.modifiers) {
-            printErrorMessage(ErrorMessages.INVALID_CLASS_MODIFIER, classElement)
-            return false
-        }
-        return true
-    }
-}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/AdapterClass.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/AdapterClass.kt
deleted file mode 100644
index bd7759a..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/AdapterClass.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.model
-
-import android.arch.lifecycle.Lifecycling
-import android.arch.lifecycle.getPackage
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-
-data class AdapterClass(val type: TypeElement,
-                        val calls: List<EventMethodCall>,
-                        val syntheticMethods: Set<ExecutableElement>)
-
-fun getAdapterName(type: TypeElement): String {
-    val packageElement = type.getPackage()
-    val qName = type.qualifiedName.toString()
-    val partialName = if (packageElement.isUnnamed) qName else qName.substring(
-            packageElement.qualifiedName.toString().length + 1)
-    return Lifecycling.getAdapterName(partialName)
-}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/EventMethod.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/EventMethod.kt
deleted file mode 100644
index a3d4712..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/EventMethod.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.model
-
-import android.arch.lifecycle.OnLifecycleEvent
-import android.arch.lifecycle.getPackageQName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-
-data class EventMethod(val method: ExecutableElement,
-                       val onLifecycleEvent: OnLifecycleEvent,
-                       val type: TypeElement) {
-
-    fun packageName() = type.getPackageQName()
-}
-
-data class EventMethodCall(val method: EventMethod, val syntheticAccess: TypeElement? = null)
\ No newline at end of file
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/InputModel.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/InputModel.kt
deleted file mode 100644
index c1e5d2b..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/InputModel.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.model
-
-import android.arch.lifecycle.name
-import android.arch.lifecycle.syntheticName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-
-data class InputModel(
-        // all java files with lifecycle annotations excluding classes from classpath
-        private val rootTypes: Set<TypeElement>,
-        // info about all lifecycle observers including classes from classpath
-        val observersInfo: Map<TypeElement, LifecycleObserverInfo>,
-        // info about generated adapters from class path
-        val generatedAdapters: Map<TypeElement, List<ExecutableElement>>) {
-
-    /**
-     *  Root class is class defined in currently processed module, not in classpath
-     */
-    fun isRootType(type: TypeElement) = type in rootTypes
-
-    fun hasSyntheticAccessorFor(eventMethod: EventMethod): Boolean {
-        val syntheticMethods = generatedAdapters[eventMethod.type] ?: return false
-        return syntheticMethods.any { executable ->
-            executable.name() == syntheticName(eventMethod.method)
-                    // same number + receiver object
-                    && (eventMethod.method.parameters.size + 1) == executable.parameters.size
-        }
-    }
-}
\ No newline at end of file
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/LifecycleObserverInfo.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/LifecycleObserverInfo.kt
deleted file mode 100644
index 1477cbf..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/model/LifecycleObserverInfo.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.model
-
-import javax.lang.model.element.TypeElement
-
-data class LifecycleObserverInfo(
-        val type: TypeElement,
-        val methods: List<EventMethod>,
-        val parents: List<LifecycleObserverInfo> = listOf())
\ No newline at end of file
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
deleted file mode 100644
index 4b93472..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import android.arch.lifecycle.model.AdapterClass
-import android.arch.lifecycle.model.EventMethod
-import android.arch.lifecycle.model.EventMethodCall
-import android.arch.lifecycle.model.InputModel
-import android.arch.lifecycle.model.LifecycleObserverInfo
-import com.google.common.collect.HashMultimap
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.TypeElement
-import javax.tools.Diagnostic
-
-private fun mergeAndVerifyMethods(processingEnv: ProcessingEnvironment,
-                                  type: TypeElement,
-                                  classMethods: List<EventMethod>,
-                                  parentMethods: List<EventMethod>): List<EventMethod> {
-    // need to update parent methods like that because:
-    // 1. visibility can be expanded
-    // 2. we want to preserve order
-    val updatedParentMethods = parentMethods.map { parentMethod ->
-        val overrideMethod = classMethods.find { (method) ->
-            processingEnv.elementUtils.overrides(method, parentMethod.method, type)
-        }
-        if (overrideMethod != null) {
-            if (overrideMethod.onLifecycleEvent != parentMethod.onLifecycleEvent) {
-                processingEnv.messager.printMessage(Diagnostic.Kind.ERROR,
-                        ErrorMessages.INVALID_STATE_OVERRIDE_METHOD, overrideMethod.method)
-            }
-            overrideMethod
-        } else {
-            parentMethod
-        }
-    }
-    return updatedParentMethods + classMethods.filterNot { updatedParentMethods.contains(it) }
-}
-
-fun flattenObservers(processingEnv: ProcessingEnvironment,
-                     world: Map<TypeElement, LifecycleObserverInfo>): List<LifecycleObserverInfo> {
-    val flattened: MutableMap<LifecycleObserverInfo, LifecycleObserverInfo> = mutableMapOf()
-
-    fun traverse(observer: LifecycleObserverInfo) {
-        if (observer in flattened) {
-            return
-        }
-        if (observer.parents.isEmpty()) {
-            flattened[observer] = observer
-            return
-        }
-        observer.parents.forEach(::traverse)
-        val methods = observer.parents
-                .map(flattened::get)
-                .fold(emptyList<EventMethod>()) { list, parentObserver ->
-                    mergeAndVerifyMethods(processingEnv, observer.type,
-                            parentObserver!!.methods, list)
-                }
-
-        flattened[observer] = LifecycleObserverInfo(observer.type,
-                mergeAndVerifyMethods(processingEnv, observer.type, observer.methods, methods))
-    }
-
-    world.values.forEach(::traverse)
-    return flattened.values.toList()
-}
-
-private fun needsSyntheticAccess(type: TypeElement, eventMethod: EventMethod): Boolean {
-    val executable = eventMethod.method
-    return type.getPackageQName() != eventMethod.packageName()
-            && (executable.isPackagePrivate() || executable.isProtected())
-}
-
-private fun validateMethod(processingEnv: ProcessingEnvironment,
-                           world: InputModel, type: TypeElement,
-                           eventMethod: EventMethod): Boolean {
-    if (!needsSyntheticAccess(type, eventMethod)) {
-        // no synthetic calls - no problems
-        return true
-    }
-
-    if (world.isRootType(eventMethod.type)) {
-        // we will generate adapters for them, so we can generate all accessors
-        return true
-    }
-
-    if (world.hasSyntheticAccessorFor(eventMethod)) {
-        // previously generated adapter already has synthetic
-        return true
-    }
-
-    processingEnv.messager.printMessage(Diagnostic.Kind.WARNING,
-            ErrorMessages.failedToGenerateAdapter(type, eventMethod), type)
-    return false
-}
-
-fun transformToOutput(processingEnv: ProcessingEnvironment,
-                      world: InputModel): List<AdapterClass> {
-    val flatObservers = flattenObservers(processingEnv, world.observersInfo)
-    val syntheticMethods = HashMultimap.create<TypeElement, EventMethodCall>()
-    val adapterCalls = flatObservers
-            // filter out everything that arrived from jars
-            .filter { (type) -> world.isRootType(type) }
-            // filter out if it needs SYNTHETIC access and we can't generate adapter for it
-            .filter { (type, methods) ->
-                methods.all { eventMethod ->
-                    validateMethod(processingEnv, world, type, eventMethod)
-                }
-            }
-            .map { (type, methods) ->
-                val calls = methods.map { eventMethod ->
-                    if (needsSyntheticAccess(type, eventMethod)) {
-                        EventMethodCall(eventMethod, eventMethod.type)
-                    } else {
-                        EventMethodCall(eventMethod)
-                    }
-                }
-                calls.filter { it.syntheticAccess != null }.forEach { eventMethod ->
-                    syntheticMethods.put(eventMethod.method.type, eventMethod)
-                }
-                type to calls
-            }.toMap()
-
-    return adapterCalls
-            .map { (type, calls) ->
-                val methods = syntheticMethods.get(type) ?: emptySet()
-                val synthetic = methods.map { eventMethod -> eventMethod!!.method.method }.toSet()
-                AdapterClass(type, calls, synthetic)
-            }
-}
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/writer.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/writer.kt
deleted file mode 100644
index 5811c61..0000000
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/writer.kt
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import android.arch.lifecycle.model.AdapterClass
-import android.arch.lifecycle.model.EventMethodCall
-import android.arch.lifecycle.model.getAdapterName
-import com.squareup.javapoet.AnnotationSpec
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.JavaFile
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.TypeElement
-import javax.tools.StandardLocation
-
-fun writeModels(infos: List<AdapterClass>, processingEnv: ProcessingEnvironment) {
-    infos.forEach({ writeAdapter(it, processingEnv) })
-}
-
-private val GENERATED_PACKAGE = "javax.annotation"
-private val GENERATED_NAME = "Generated"
-private val LIFECYCLE_EVENT = Lifecycle.Event::class.java
-
-private val T = "\$T"
-private val N = "\$N"
-private val L = "\$L"
-private val S = "\$S"
-
-private val OWNER_PARAM: ParameterSpec = ParameterSpec.builder(
-        ClassName.get(LifecycleOwner::class.java), "owner").build()
-private val EVENT_PARAM: ParameterSpec = ParameterSpec.builder(
-        ClassName.get(LIFECYCLE_EVENT), "event").build()
-private val ON_ANY_PARAM: ParameterSpec = ParameterSpec.builder(TypeName.BOOLEAN, "onAny").build()
-
-private val METHODS_LOGGER: ParameterSpec = ParameterSpec.builder(
-        ClassName.get(MethodCallsLogger::class.java), "logger").build()
-
-private const val HAS_LOGGER_VAR = "hasLogger"
-
-private fun writeAdapter(adapter: AdapterClass, processingEnv: ProcessingEnvironment) {
-    val receiverField: FieldSpec = FieldSpec.builder(ClassName.get(adapter.type), "mReceiver",
-            Modifier.FINAL).build()
-    val dispatchMethodBuilder = MethodSpec.methodBuilder("callMethods")
-            .returns(TypeName.VOID)
-            .addParameter(OWNER_PARAM)
-            .addParameter(EVENT_PARAM)
-            .addParameter(ON_ANY_PARAM)
-            .addParameter(METHODS_LOGGER)
-            .addModifiers(Modifier.PUBLIC)
-            .addAnnotation(Override::class.java)
-    val dispatchMethod = dispatchMethodBuilder.apply {
-
-        addStatement("boolean $L = $N != null", HAS_LOGGER_VAR, METHODS_LOGGER)
-        val callsByEventType = adapter.calls.groupBy { it.method.onLifecycleEvent.value }
-        beginControlFlow("if ($N)", ON_ANY_PARAM).apply {
-            writeMethodCalls(callsByEventType[Lifecycle.Event.ON_ANY] ?: emptyList(), receiverField)
-        }.endControlFlow()
-
-        callsByEventType
-                .filterKeys { key -> key != Lifecycle.Event.ON_ANY }
-                .forEach { (event, calls) ->
-                    beginControlFlow("if ($N == $T.$L)", EVENT_PARAM, LIFECYCLE_EVENT, event)
-                    writeMethodCalls(calls, receiverField)
-                    endControlFlow()
-                }
-    }.build()
-
-    val receiverParam = ParameterSpec.builder(
-            ClassName.get(adapter.type), "receiver").build()
-
-    val syntheticMethods = adapter.syntheticMethods.map {
-        val method = MethodSpec.methodBuilder(syntheticName(it))
-                .returns(TypeName.VOID)
-                .addModifiers(Modifier.PUBLIC)
-                .addModifiers(Modifier.STATIC)
-                .addParameter(receiverParam)
-        if (it.parameters.size >= 1) {
-            method.addParameter(OWNER_PARAM)
-        }
-        if (it.parameters.size == 2) {
-            method.addParameter(EVENT_PARAM)
-        }
-
-        val count = it.parameters.size
-        val paramString = generateParamString(count)
-        method.addStatement("$N.$L($paramString)", receiverParam, it.name(),
-                *takeParams(count, OWNER_PARAM, EVENT_PARAM))
-        method.build()
-    }
-
-    val constructor = MethodSpec.constructorBuilder()
-            .addParameter(receiverParam)
-            .addStatement("this.$N = $N", receiverField, receiverParam)
-            .build()
-
-    val adapterName = getAdapterName(adapter.type)
-    val adapterTypeSpecBuilder = TypeSpec.classBuilder(adapterName)
-            .addModifiers(Modifier.PUBLIC)
-            .addSuperinterface(ClassName.get(GeneratedAdapter::class.java))
-            .addField(receiverField)
-            .addMethod(constructor)
-            .addMethod(dispatchMethod)
-            .addMethods(syntheticMethods)
-
-    addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder, processingEnv)
-
-    JavaFile.builder(adapter.type.getPackageQName(), adapterTypeSpecBuilder.build())
-            .build().writeTo(processingEnv.filer)
-
-    generateKeepRule(adapter.type, processingEnv)
-}
-
-private fun addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder: TypeSpec.Builder,
-                                              processingEnv: ProcessingEnvironment) {
-    val generatedAnnotationAvailable = processingEnv
-            .elementUtils
-            .getTypeElement(GENERATED_PACKAGE + "." + GENERATED_NAME) != null
-    if (generatedAnnotationAvailable) {
-        val generatedAnnotationSpec =
-                AnnotationSpec.builder(ClassName.get(GENERATED_PACKAGE, GENERATED_NAME)).addMember(
-                        "value",
-                        S,
-                        LifecycleProcessor::class.java.canonicalName).build()
-        adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
-    }
-}
-
-private fun generateKeepRule(type: TypeElement, processingEnv: ProcessingEnvironment) {
-    val adapterClass = type.getPackageQName() + "." + getAdapterName(type)
-    val observerClass = type.toString()
-    val keepRule = """# Generated keep rule for Lifecycle observer adapter.
-        |-if class $observerClass {
-        |    <init>(...);
-        |}
-        |-keep class $adapterClass {
-        |    <init>(...);
-        |}
-        |""".trimMargin()
-
-    // Write the keep rule to the META-INF/proguard directory of the Jar file. The file name
-    // contains the fully qualified observer name so that file names are unique. This will allow any
-    // jar file merging to not overwrite keep rule files.
-    val path = "META-INF/proguard/$observerClass.pro"
-    val out = processingEnv.filer.createResource(StandardLocation.CLASS_OUTPUT, "", path)
-    out.openWriter().use { it.write(keepRule) }
-}
-
-private fun MethodSpec.Builder.writeMethodCalls(calls: List<EventMethodCall>,
-                                                receiverField: FieldSpec) {
-    calls.forEach { (method, syntheticAccess) ->
-        val count = method.method.parameters.size
-        val callType = 1 shl count
-        val methodName = method.method.name()
-        beginControlFlow("if (!$L || $N.approveCall($S, $callType))",
-                HAS_LOGGER_VAR, METHODS_LOGGER, methodName).apply {
-
-            if (syntheticAccess == null) {
-                val paramString = generateParamString(count)
-                addStatement("$N.$L($paramString)", receiverField,
-                        methodName,
-                        *takeParams(count, OWNER_PARAM, EVENT_PARAM))
-            } else {
-                val originalType = syntheticAccess
-                val paramString = generateParamString(count + 1)
-                val className = ClassName.get(originalType.getPackageQName(),
-                        getAdapterName(originalType))
-                addStatement("$T.$L($paramString)", className,
-                        syntheticName(method.method),
-                        *takeParams(count + 1, receiverField, OWNER_PARAM, EVENT_PARAM))
-            }
-        }.endControlFlow()
-    }
-    addStatement("return")
-}
-
-private fun takeParams(count: Int, vararg params: Any) = params.take(count).toTypedArray()
-
-private fun generateParamString(count: Int) = (0 until count).joinToString(",") { N }
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/ErrorMessages.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/ErrorMessages.kt
new file mode 100644
index 0000000..5a5df7f
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/ErrorMessages.kt
@@ -0,0 +1,45 @@
+/*
+ * 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 androidx.lifecycle
+
+import androidx.lifecycle.model.EventMethod
+import javax.lang.model.element.TypeElement
+
+object ErrorMessages {
+    const val TOO_MANY_ARGS = "callback method cannot have more than 2 parameters"
+    const val TOO_MANY_ARGS_NOT_ON_ANY = "only callback annotated with ON_ANY " +
+            "can have 2 parameters"
+    const val INVALID_SECOND_ARGUMENT = "2nd argument of a callback method" +
+            " must be Lifecycle.Event and represent the current event"
+    const val INVALID_FIRST_ARGUMENT = "1st argument of a callback method must be " +
+            "a LifecycleOwner which represents the source of the event"
+    const val INVALID_METHOD_MODIFIER = "method marked with OnLifecycleEvent annotation can " +
+            "not be private"
+    const val INVALID_CLASS_MODIFIER = "class containing OnLifecycleEvent methods can not be " +
+            "private"
+    const val INVALID_STATE_OVERRIDE_METHOD = "overridden method must handle the same " +
+            "onState changes as original method"
+    const val INVALID_ENCLOSING_ELEMENT =
+            "Parent of OnLifecycleEvent should be a class or interface"
+    const val INVALID_ANNOTATED_ELEMENT = "OnLifecycleEvent can only be added to methods"
+
+    fun failedToGenerateAdapter(type: TypeElement, failureReason: EventMethod) =
+            """
+             Failed to generate an Adapter for $type, because it needs to be able to access to
+             package private method ${failureReason.method.name()} from ${failureReason.type}
+            """.trim()
+}
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/LifecycleProcessor.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/LifecycleProcessor.kt
new file mode 100644
index 0000000..717afa4
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/LifecycleProcessor.kt
@@ -0,0 +1,37 @@
+/*
+ * 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 androidx.lifecycle
+
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.annotation.processing.SupportedAnnotationTypes
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.TypeElement
+
+@SupportedAnnotationTypes("androidx.lifecycle.OnLifecycleEvent")
+class LifecycleProcessor : AbstractProcessor() {
+    override fun process(annotations: MutableSet<out TypeElement>,
+                         roundEnv: RoundEnvironment): Boolean {
+        val input = collectAndVerifyInput(processingEnv, roundEnv)
+        writeModels(transformToOutput(processingEnv, input), processingEnv)
+        return true
+    }
+
+    override fun getSupportedSourceVersion(): SourceVersion {
+        return SourceVersion.latest()
+    }
+}
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/elements_ext.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/elements_ext.kt
new file mode 100644
index 0000000..9f8adad
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/elements_ext.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 androidx.lifecycle
+
+import com.google.auto.common.MoreElements
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.util.ElementFilter
+
+fun Element.getPackage(): PackageElement = MoreElements.getPackage(this)
+
+fun Element.getPackageQName() = getPackage().qualifiedName.toString()
+
+fun ExecutableElement.name() = simpleName.toString()
+
+fun ExecutableElement.isPackagePrivate() = !modifiers.any {
+    it == Modifier.PUBLIC || it == Modifier.PROTECTED || it == Modifier.PRIVATE
+}
+
+fun ExecutableElement.isProtected() = modifiers.contains(Modifier.PROTECTED)
+
+fun TypeElement.methods(): List<ExecutableElement> = ElementFilter.methodsIn(enclosedElements)
+
+private const val SYNTHETIC = "__synthetic_"
+
+fun syntheticName(method: ExecutableElement) = "$SYNTHETIC${method.simpleName}"
+
+fun isSyntheticMethod(method: ExecutableElement) = method.name().startsWith(SYNTHETIC)
+
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/input_collector.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/input_collector.kt
new file mode 100644
index 0000000..16546f5
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/input_collector.kt
@@ -0,0 +1,168 @@
+/*
+ * 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 androidx.lifecycle
+
+import androidx.lifecycle.model.EventMethod
+import androidx.lifecycle.model.InputModel
+import androidx.lifecycle.model.LifecycleObserverInfo
+import androidx.lifecycle.model.getAdapterName
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import javax.tools.Diagnostic
+
+fun collectAndVerifyInput(processingEnv: ProcessingEnvironment,
+                          roundEnv: RoundEnvironment): InputModel {
+    val validator = Validator(processingEnv)
+    val worldCollector = ObserversCollector(processingEnv)
+    val roots = roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).map { elem ->
+        if (elem.kind != ElementKind.METHOD) {
+            validator.printErrorMessage(ErrorMessages.INVALID_ANNOTATED_ELEMENT, elem)
+            null
+        } else {
+            val enclosingElement = elem.enclosingElement
+            if (validator.validateClass(enclosingElement)) {
+                MoreElements.asType(enclosingElement)
+            } else {
+                null
+            }
+        }
+    }.filterNotNull().toSet()
+    roots.forEach { worldCollector.collect(it) }
+    val observersInfo = worldCollector.observers
+    val generatedAdapters = worldCollector.observers.keys
+            .mapNotNull { type ->
+                worldCollector.generatedAdapterInfoFor(type)?.let { type to it }
+            }.toMap()
+    return InputModel(roots, observersInfo, generatedAdapters)
+}
+
+class ObserversCollector(processingEnv: ProcessingEnvironment) {
+    val typeUtils: Types = processingEnv.typeUtils
+    val elementUtils: Elements = processingEnv.elementUtils
+    val lifecycleObserverTypeMirror: TypeMirror =
+            elementUtils.getTypeElement(LifecycleObserver::class.java.canonicalName).asType()
+    val validator = Validator(processingEnv)
+    val observers: MutableMap<TypeElement, LifecycleObserverInfo> = mutableMapOf()
+
+    fun collect(type: TypeElement): LifecycleObserverInfo? {
+        if (type in observers) {
+            return observers[type]
+        }
+        val parents = (listOf(type.superclass) + type.interfaces)
+                .filter { typeUtils.isAssignable(it, lifecycleObserverTypeMirror) }
+                .filterNot { typeUtils.isSameType(it, lifecycleObserverTypeMirror) }
+                .map { collect(MoreTypes.asTypeElement(it)) }
+                .filterNotNull()
+        val info = createObserverInfo(type, parents)
+        if (info != null) {
+            observers[type] = info
+        }
+        return info
+    }
+
+    fun generatedAdapterInfoFor(type: TypeElement): List<ExecutableElement>? {
+        val packageName = if (type.getPackageQName().isEmpty()) "" else "${type.getPackageQName()}."
+        val adapterType = elementUtils.getTypeElement(packageName + getAdapterName(type))
+        return adapterType?.methods()
+                ?.filter { executable -> isSyntheticMethod(executable) }
+    }
+
+    private fun createObserverInfo(typeElement: TypeElement,
+                                   parents: List<LifecycleObserverInfo>): LifecycleObserverInfo? {
+        if (!validator.validateClass(typeElement)) {
+            return null
+        }
+        val methods = typeElement.methods().filter { executable ->
+            MoreElements.isAnnotationPresent(executable, OnLifecycleEvent::class.java)
+        }.map { executable ->
+            val onState = executable.getAnnotation(OnLifecycleEvent::class.java)
+            if (validator.validateMethod(executable, onState.value)) {
+                EventMethod(executable, onState, typeElement)
+            } else {
+                null
+            }
+        }.filterNotNull()
+        return LifecycleObserverInfo(typeElement, methods, parents)
+    }
+}
+
+class Validator(val processingEnv: ProcessingEnvironment) {
+
+    fun printErrorMessage(msg: CharSequence, elem: Element) {
+        processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, msg, elem)
+    }
+
+    fun validateParam(param: VariableElement,
+                      expectedType: Class<*>, errorMsg: String): Boolean {
+        if (!MoreTypes.isTypeOf(expectedType, param.asType())) {
+            printErrorMessage(errorMsg, param)
+            return false
+        }
+        return true
+    }
+
+    fun validateMethod(method: ExecutableElement, event: Lifecycle.Event): Boolean {
+        if (Modifier.PRIVATE in method.modifiers) {
+            printErrorMessage(ErrorMessages.INVALID_METHOD_MODIFIER, method)
+            return false
+        }
+        val params = method.parameters
+        if ((params.size > 2)) {
+            printErrorMessage(ErrorMessages.TOO_MANY_ARGS, method)
+            return false
+        }
+
+        if (params.size == 2 && event != Lifecycle.Event.ON_ANY) {
+            printErrorMessage(ErrorMessages.TOO_MANY_ARGS_NOT_ON_ANY, method)
+            return false
+        }
+
+        if (params.size == 2 && !validateParam(params[1], Lifecycle.Event::class.java,
+                ErrorMessages.INVALID_SECOND_ARGUMENT)) {
+            return false
+        }
+
+        if (params.size > 0) {
+            return validateParam(params[0], LifecycleOwner::class.java,
+                    ErrorMessages.INVALID_FIRST_ARGUMENT)
+        }
+        return true
+    }
+
+    fun validateClass(classElement: Element): Boolean {
+        if (!MoreElements.isType(classElement)) {
+            printErrorMessage(ErrorMessages.INVALID_ENCLOSING_ELEMENT, classElement)
+            return false
+        }
+        if (Modifier.PRIVATE in classElement.modifiers) {
+            printErrorMessage(ErrorMessages.INVALID_CLASS_MODIFIER, classElement)
+            return false
+        }
+        return true
+    }
+}
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/AdapterClass.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/AdapterClass.kt
new file mode 100644
index 0000000..0bd1c5e
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/AdapterClass.kt
@@ -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 androidx.lifecycle.model
+
+import androidx.lifecycle.Lifecycling
+import androidx.lifecycle.getPackage
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+
+data class AdapterClass(val type: TypeElement,
+                        val calls: List<EventMethodCall>,
+                        val syntheticMethods: Set<ExecutableElement>)
+
+fun getAdapterName(type: TypeElement): String {
+    val packageElement = type.getPackage()
+    val qName = type.qualifiedName.toString()
+    val partialName = if (packageElement.isUnnamed) qName else qName.substring(
+            packageElement.qualifiedName.toString().length + 1)
+    return Lifecycling.getAdapterName(partialName)
+}
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/EventMethod.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/EventMethod.kt
new file mode 100644
index 0000000..1056e71
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/EventMethod.kt
@@ -0,0 +1,31 @@
+/*
+ * 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 androidx.lifecycle.model
+
+import androidx.lifecycle.OnLifecycleEvent
+import androidx.lifecycle.getPackageQName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+
+data class EventMethod(val method: ExecutableElement,
+                       val onLifecycleEvent: OnLifecycleEvent,
+                       val type: TypeElement) {
+
+    fun packageName() = type.getPackageQName()
+}
+
+data class EventMethodCall(val method: EventMethod, val syntheticAccess: TypeElement? = null)
\ No newline at end of file
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/InputModel.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/InputModel.kt
new file mode 100644
index 0000000..4b3aae9
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/InputModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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 androidx.lifecycle.model
+
+import androidx.lifecycle.name
+import androidx.lifecycle.syntheticName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+
+data class InputModel(
+        // all java files with lifecycle annotations excluding classes from classpath
+        private val rootTypes: Set<TypeElement>,
+        // info about all lifecycle observers including classes from classpath
+        val observersInfo: Map<TypeElement, LifecycleObserverInfo>,
+        // info about generated adapters from class path
+        val generatedAdapters: Map<TypeElement, List<ExecutableElement>>) {
+
+    /**
+     *  Root class is class defined in currently processed module, not in classpath
+     */
+    fun isRootType(type: TypeElement) = type in rootTypes
+
+    fun hasSyntheticAccessorFor(eventMethod: EventMethod): Boolean {
+        val syntheticMethods = generatedAdapters[eventMethod.type] ?: return false
+        return syntheticMethods.any { executable ->
+            executable.name() == syntheticName(eventMethod.method)
+                    // same number + receiver object
+                    && (eventMethod.method.parameters.size + 1) == executable.parameters.size
+        }
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/LifecycleObserverInfo.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/LifecycleObserverInfo.kt
new file mode 100644
index 0000000..59b3c66
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/model/LifecycleObserverInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * 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 androidx.lifecycle.model
+
+import javax.lang.model.element.TypeElement
+
+data class LifecycleObserverInfo(
+        val type: TypeElement,
+        val methods: List<EventMethod>,
+        val parents: List<LifecycleObserverInfo> = listOf())
\ No newline at end of file
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/transformation.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/transformation.kt
new file mode 100644
index 0000000..afb1e29
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/transformation.kt
@@ -0,0 +1,143 @@
+/*
+ * 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 androidx.lifecycle
+
+import androidx.lifecycle.model.AdapterClass
+import androidx.lifecycle.model.EventMethod
+import androidx.lifecycle.model.EventMethodCall
+import androidx.lifecycle.model.InputModel
+import androidx.lifecycle.model.LifecycleObserverInfo
+import com.google.common.collect.HashMultimap
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic
+
+private fun mergeAndVerifyMethods(processingEnv: ProcessingEnvironment,
+                                  type: TypeElement,
+                                  classMethods: List<EventMethod>,
+                                  parentMethods: List<EventMethod>): List<EventMethod> {
+    // need to update parent methods like that because:
+    // 1. visibility can be expanded
+    // 2. we want to preserve order
+    val updatedParentMethods = parentMethods.map { parentMethod ->
+        val overrideMethod = classMethods.find { (method) ->
+            processingEnv.elementUtils.overrides(method, parentMethod.method, type)
+        }
+        if (overrideMethod != null) {
+            if (overrideMethod.onLifecycleEvent != parentMethod.onLifecycleEvent) {
+                processingEnv.messager.printMessage(Diagnostic.Kind.ERROR,
+                        ErrorMessages.INVALID_STATE_OVERRIDE_METHOD, overrideMethod.method)
+            }
+            overrideMethod
+        } else {
+            parentMethod
+        }
+    }
+    return updatedParentMethods + classMethods.filterNot { updatedParentMethods.contains(it) }
+}
+
+fun flattenObservers(processingEnv: ProcessingEnvironment,
+                     world: Map<TypeElement, LifecycleObserverInfo>): List<LifecycleObserverInfo> {
+    val flattened: MutableMap<LifecycleObserverInfo, LifecycleObserverInfo> = mutableMapOf()
+
+    fun traverse(observer: LifecycleObserverInfo) {
+        if (observer in flattened) {
+            return
+        }
+        if (observer.parents.isEmpty()) {
+            flattened[observer] = observer
+            return
+        }
+        observer.parents.forEach(::traverse)
+        val methods = observer.parents
+                .map(flattened::get)
+                .fold(emptyList<EventMethod>()) { list, parentObserver ->
+                    mergeAndVerifyMethods(processingEnv, observer.type,
+                            parentObserver!!.methods, list)
+                }
+
+        flattened[observer] = LifecycleObserverInfo(observer.type,
+                mergeAndVerifyMethods(processingEnv, observer.type, observer.methods, methods))
+    }
+
+    world.values.forEach(::traverse)
+    return flattened.values.toList()
+}
+
+private fun needsSyntheticAccess(type: TypeElement, eventMethod: EventMethod): Boolean {
+    val executable = eventMethod.method
+    return type.getPackageQName() != eventMethod.packageName()
+            && (executable.isPackagePrivate() || executable.isProtected())
+}
+
+private fun validateMethod(processingEnv: ProcessingEnvironment,
+                           world: InputModel, type: TypeElement,
+                           eventMethod: EventMethod): Boolean {
+    if (!needsSyntheticAccess(type, eventMethod)) {
+        // no synthetic calls - no problems
+        return true
+    }
+
+    if (world.isRootType(eventMethod.type)) {
+        // we will generate adapters for them, so we can generate all accessors
+        return true
+    }
+
+    if (world.hasSyntheticAccessorFor(eventMethod)) {
+        // previously generated adapter already has synthetic
+        return true
+    }
+
+    processingEnv.messager.printMessage(Diagnostic.Kind.WARNING,
+            ErrorMessages.failedToGenerateAdapter(type, eventMethod), type)
+    return false
+}
+
+fun transformToOutput(processingEnv: ProcessingEnvironment,
+                      world: InputModel): List<AdapterClass> {
+    val flatObservers = flattenObservers(processingEnv, world.observersInfo)
+    val syntheticMethods = HashMultimap.create<TypeElement, EventMethodCall>()
+    val adapterCalls = flatObservers
+            // filter out everything that arrived from jars
+            .filter { (type) -> world.isRootType(type) }
+            // filter out if it needs SYNTHETIC access and we can't generate adapter for it
+            .filter { (type, methods) ->
+                methods.all { eventMethod ->
+                    validateMethod(processingEnv, world, type, eventMethod)
+                }
+            }
+            .map { (type, methods) ->
+                val calls = methods.map { eventMethod ->
+                    if (needsSyntheticAccess(type, eventMethod)) {
+                        EventMethodCall(eventMethod, eventMethod.type)
+                    } else {
+                        EventMethodCall(eventMethod)
+                    }
+                }
+                calls.filter { it.syntheticAccess != null }.forEach { eventMethod ->
+                    syntheticMethods.put(eventMethod.method.type, eventMethod)
+                }
+                type to calls
+            }.toMap()
+
+    return adapterCalls
+            .map { (type, calls) ->
+                val methods = syntheticMethods.get(type) ?: emptySet()
+                val synthetic = methods.map { eventMethod -> eventMethod!!.method.method }.toSet()
+                AdapterClass(type, calls, synthetic)
+            }
+}
diff --git a/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/writer.kt b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/writer.kt
new file mode 100644
index 0000000..2d2d45d
--- /dev/null
+++ b/lifecycle/compiler/src/main/kotlin/androidx/lifecycle/writer.kt
@@ -0,0 +1,197 @@
+/*
+ * 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 androidx.lifecycle
+
+import androidx.lifecycle.model.AdapterClass
+import androidx.lifecycle.model.EventMethodCall
+import androidx.lifecycle.model.getAdapterName
+import com.squareup.javapoet.AnnotationSpec
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.tools.StandardLocation
+
+fun writeModels(infos: List<AdapterClass>, processingEnv: ProcessingEnvironment) {
+    infos.forEach({ writeAdapter(it, processingEnv) })
+}
+
+private val GENERATED_PACKAGE = "javax.annotation"
+private val GENERATED_NAME = "Generated"
+private val LIFECYCLE_EVENT = Lifecycle.Event::class.java
+
+private val T = "\$T"
+private val N = "\$N"
+private val L = "\$L"
+private val S = "\$S"
+
+private val OWNER_PARAM: ParameterSpec = ParameterSpec.builder(
+        ClassName.get(LifecycleOwner::class.java), "owner").build()
+private val EVENT_PARAM: ParameterSpec = ParameterSpec.builder(
+        ClassName.get(LIFECYCLE_EVENT), "event").build()
+private val ON_ANY_PARAM: ParameterSpec = ParameterSpec.builder(TypeName.BOOLEAN, "onAny").build()
+
+private val METHODS_LOGGER: ParameterSpec = ParameterSpec.builder(
+        ClassName.get(MethodCallsLogger::class.java), "logger").build()
+
+private const val HAS_LOGGER_VAR = "hasLogger"
+
+private fun writeAdapter(adapter: AdapterClass, processingEnv: ProcessingEnvironment) {
+    val receiverField: FieldSpec = FieldSpec.builder(ClassName.get(adapter.type), "mReceiver",
+            Modifier.FINAL).build()
+    val dispatchMethodBuilder = MethodSpec.methodBuilder("callMethods")
+            .returns(TypeName.VOID)
+            .addParameter(OWNER_PARAM)
+            .addParameter(EVENT_PARAM)
+            .addParameter(ON_ANY_PARAM)
+            .addParameter(METHODS_LOGGER)
+            .addModifiers(Modifier.PUBLIC)
+            .addAnnotation(Override::class.java)
+    val dispatchMethod = dispatchMethodBuilder.apply {
+
+        addStatement("boolean $L = $N != null", HAS_LOGGER_VAR, METHODS_LOGGER)
+        val callsByEventType = adapter.calls.groupBy { it.method.onLifecycleEvent.value }
+        beginControlFlow("if ($N)", ON_ANY_PARAM).apply {
+            writeMethodCalls(callsByEventType[Lifecycle.Event.ON_ANY] ?: emptyList(), receiverField)
+        }.endControlFlow()
+
+        callsByEventType
+                .filterKeys { key -> key != Lifecycle.Event.ON_ANY }
+                .forEach { (event, calls) ->
+                    beginControlFlow("if ($N == $T.$L)", EVENT_PARAM, LIFECYCLE_EVENT, event)
+                    writeMethodCalls(calls, receiverField)
+                    endControlFlow()
+                }
+    }.build()
+
+    val receiverParam = ParameterSpec.builder(
+            ClassName.get(adapter.type), "receiver").build()
+
+    val syntheticMethods = adapter.syntheticMethods.map {
+        val method = MethodSpec.methodBuilder(syntheticName(it))
+                .returns(TypeName.VOID)
+                .addModifiers(Modifier.PUBLIC)
+                .addModifiers(Modifier.STATIC)
+                .addParameter(receiverParam)
+        if (it.parameters.size >= 1) {
+            method.addParameter(OWNER_PARAM)
+        }
+        if (it.parameters.size == 2) {
+            method.addParameter(EVENT_PARAM)
+        }
+
+        val count = it.parameters.size
+        val paramString = generateParamString(count)
+        method.addStatement("$N.$L($paramString)", receiverParam, it.name(),
+                *takeParams(count, OWNER_PARAM, EVENT_PARAM))
+        method.build()
+    }
+
+    val constructor = MethodSpec.constructorBuilder()
+            .addParameter(receiverParam)
+            .addStatement("this.$N = $N", receiverField, receiverParam)
+            .build()
+
+    val adapterName = getAdapterName(adapter.type)
+    val adapterTypeSpecBuilder = TypeSpec.classBuilder(adapterName)
+            .addModifiers(Modifier.PUBLIC)
+            .addSuperinterface(ClassName.get(GeneratedAdapter::class.java))
+            .addField(receiverField)
+            .addMethod(constructor)
+            .addMethod(dispatchMethod)
+            .addMethods(syntheticMethods)
+
+    addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder, processingEnv)
+
+    JavaFile.builder(adapter.type.getPackageQName(), adapterTypeSpecBuilder.build())
+            .build().writeTo(processingEnv.filer)
+
+    generateKeepRule(adapter.type, processingEnv)
+}
+
+private fun addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder: TypeSpec.Builder,
+                                              processingEnv: ProcessingEnvironment) {
+    val generatedAnnotationAvailable = processingEnv
+            .elementUtils
+            .getTypeElement(GENERATED_PACKAGE + "." + GENERATED_NAME) != null
+    if (generatedAnnotationAvailable) {
+        val generatedAnnotationSpec =
+                AnnotationSpec.builder(ClassName.get(GENERATED_PACKAGE, GENERATED_NAME)).addMember(
+                        "value",
+                        S,
+                        LifecycleProcessor::class.java.canonicalName).build()
+        adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
+    }
+}
+
+private fun generateKeepRule(type: TypeElement, processingEnv: ProcessingEnvironment) {
+    val adapterClass = type.getPackageQName() + "." + getAdapterName(type)
+    val observerClass = type.toString()
+    val keepRule = """# Generated keep rule for Lifecycle observer adapter.
+        |-if class $observerClass {
+        |    <init>(...);
+        |}
+        |-keep class $adapterClass {
+        |    <init>(...);
+        |}
+        |""".trimMargin()
+
+    // Write the keep rule to the META-INF/proguard directory of the Jar file. The file name
+    // contains the fully qualified observer name so that file names are unique. This will allow any
+    // jar file merging to not overwrite keep rule files.
+    val path = "META-INF/proguard/$observerClass.pro"
+    val out = processingEnv.filer.createResource(StandardLocation.CLASS_OUTPUT, "", path)
+    out.openWriter().use { it.write(keepRule) }
+}
+
+private fun MethodSpec.Builder.writeMethodCalls(calls: List<EventMethodCall>,
+                                                receiverField: FieldSpec) {
+    calls.forEach { (method, syntheticAccess) ->
+        val count = method.method.parameters.size
+        val callType = 1 shl count
+        val methodName = method.method.name()
+        beginControlFlow("if (!$L || $N.approveCall($S, $callType))",
+                HAS_LOGGER_VAR, METHODS_LOGGER, methodName).apply {
+
+            if (syntheticAccess == null) {
+                val paramString = generateParamString(count)
+                addStatement("$N.$L($paramString)", receiverField,
+                        methodName,
+                        *takeParams(count, OWNER_PARAM, EVENT_PARAM))
+            } else {
+                val originalType = syntheticAccess
+                val paramString = generateParamString(count + 1)
+                val className = ClassName.get(originalType.getPackageQName(),
+                        getAdapterName(originalType))
+                addStatement("$T.$L($paramString)", className,
+                        syntheticName(method.method),
+                        *takeParams(count + 1, receiverField, OWNER_PARAM, EVENT_PARAM))
+            }
+        }.endControlFlow()
+    }
+    addStatement("return")
+}
+
+private fun takeParams(count: Int, vararg params: Any) = params.take(count).toTypedArray()
+
+private fun generateParamString(count: Int) = (0 until count).joinToString(",") { N }
diff --git a/lifecycle/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/lifecycle/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
index cdabd71..2acce7d 100644
--- a/lifecycle/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
+++ b/lifecycle/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -1 +1 @@
-android.arch.lifecycle.LifecycleProcessor
+androidx.lifecycle.LifecycleProcessor
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/InvalidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/InvalidCasesTest.kt
deleted file mode 100644
index 3e502d3..0000000
--- a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/InvalidCasesTest.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import android.arch.lifecycle.utils.processClass
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-class InvalidCasesTest(val name: String, val errorMsg: String) {
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "failingCase({0})")
-        fun data() : Collection<Array<Any>> = listOf(
-                arrayOf<Any>("foo.InvalidFirstArg1", ErrorMessages.INVALID_FIRST_ARGUMENT),
-                arrayOf<Any>("foo.InvalidFirstArg2", ErrorMessages.INVALID_FIRST_ARGUMENT),
-                arrayOf<Any>("foo.InvalidSecondArg", ErrorMessages.INVALID_SECOND_ARGUMENT),
-                arrayOf<Any>("foo.TooManyArgs1", ErrorMessages.TOO_MANY_ARGS),
-                arrayOf<Any>("foo.TooManyArgs2", ErrorMessages.TOO_MANY_ARGS_NOT_ON_ANY),
-                arrayOf<Any>("foo.InvalidMethodModifier",
-                        ErrorMessages.INVALID_METHOD_MODIFIER),
-                arrayOf<Any>("foo.InvalidClassModifier", ErrorMessages.INVALID_CLASS_MODIFIER),
-                arrayOf<Any>("foo.InvalidInheritance1",
-                        ErrorMessages.INVALID_STATE_OVERRIDE_METHOD),
-                arrayOf<Any>("foo.InvalidInheritance2",
-                        ErrorMessages.INVALID_STATE_OVERRIDE_METHOD)
-        )
-    }
-
-    @Test
-    fun shouldFailWithError() {
-        processClass(name).failsToCompile().withErrorContaining(errorMsg)
-    }
-}
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
deleted file mode 100644
index 83d69e0..0000000
--- a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle
-
-import android.arch.lifecycle.utils.load
-import android.arch.lifecycle.utils.processClass
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaSourcesSubject
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import java.io.File
-import java.lang.Exception
-import java.net.URLClassLoader
-import javax.tools.StandardLocation
-
-@RunWith(JUnit4::class)
-class ValidCasesTest {
-    @Test
-    fun testTest() {
-        processClass("foo.Bar").compilesWithoutError()
-    }
-
-    @Test
-    fun testOnAny() {
-        processClass("foo.OnAnyMethod").compilesWithoutError().and().generatesSources(
-                load("foo.OnAnyMethod_LifecycleAdapter", "expected")
-        ).and().generatesProGuardRule("foo.OnAnyMethod.pro")
-    }
-
-    @Test
-    fun testInheritance() {
-        processClass("foo.InheritanceOk1").compilesWithoutError()
-    }
-
-    @Test
-    fun testInheritance2() {
-        processClass("foo.InheritanceOk2").compilesWithoutError().and().generatesSources(
-                load("foo.InheritanceOk2Base_LifecycleAdapter", "expected"),
-                load("foo.InheritanceOk2Derived_LifecycleAdapter", "expected")
-        )
-                .and().generatesProGuardRule("foo.InheritanceOk2Base.pro")
-                .and().generatesProGuardRule("foo.InheritanceOk2Derived.pro")
-    }
-
-    @Test
-    fun testInheritance3() {
-        processClass("foo.InheritanceOk3").compilesWithoutError().and().generatesSources(
-                load("foo.InheritanceOk3Base_LifecycleAdapter", "expected"),
-                load("foo.InheritanceOk3Derived_LifecycleAdapter", "expected")
-        )
-                .and().generatesProGuardRule("foo.InheritanceOk3Base.pro")
-                .and().generatesProGuardRule("foo.InheritanceOk3Derived.pro")
-    }
-
-    @Test
-    fun testNoPackageClass() {
-        processClass("NoPackageOk").compilesWithoutError()
-    }
-
-    @Test
-    fun testInterface1() {
-        processClass("foo.InterfaceOk1").compilesWithoutError()
-    }
-
-    @Test
-    fun testInterface2() {
-        processClass("foo.InterfaceOk2").compilesWithoutError().and().generatesSources(
-                load("foo.InterfaceOk2Base_LifecycleAdapter", "expected"),
-                load("foo.InterfaceOk2Derived_LifecycleAdapter", "expected"),
-                load("foo.InterfaceOk2Interface_LifecycleAdapter", "expected")
-        )
-                .and().generatesProGuardRule("foo.InterfaceOk2Base.pro")
-                .and().generatesProGuardRule("foo.InterfaceOk2Derived.pro")
-                .and().generatesProGuardRule("foo.InterfaceOk2Interface.pro")
-    }
-
-    @Test
-    fun testInheritanceDifferentPackages1() {
-        processClass("foo.DifferentPackagesBase1",
-                "bar.DifferentPackagesDerived1").compilesWithoutError().and().generatesSources(
-                load("foo.DifferentPackagesBase1_LifecycleAdapter", "expected"),
-                load("bar.DifferentPackagesDerived1_LifecycleAdapter", "expected")
-        )
-                .and().generatesProGuardRule("foo.DifferentPackagesBase1.pro")
-                .and().generatesProGuardRule("bar.DifferentPackagesDerived1.pro")
-    }
-
-    @Test
-    fun testInheritanceDifferentPackages2() {
-        processClass("foo.DifferentPackagesBase2",
-                "bar.DifferentPackagesDerived2").compilesWithoutError().and().generatesSources(
-                load("foo.DifferentPackagesBase2_LifecycleAdapter", "expected"),
-                load("bar.DifferentPackagesDerived2_LifecycleAdapter", "expected")
-        )
-                .and().generatesProGuardRule("foo.DifferentPackagesPreBase2.pro")
-                .and().generatesProGuardRule("foo.DifferentPackagesBase2.pro")
-                .and().generatesProGuardRule("bar.DifferentPackagesDerived2.pro")
-    }
-
-    private fun <T> CompileTester.GeneratedPredicateClause<T>.generatesProGuardRule(name: String):
-            CompileTester.SuccessfulFileClause<T> {
-        return generatesFileNamed(StandardLocation.CLASS_OUTPUT, "", "META-INF/proguard/$name")
-    }
-
-    @Test
-    fun testJar() {
-        JavaSourcesSubject.assertThat(load("foo.DerivedFromJar", ""))
-                .withClasspathFrom(libraryClassLoader())
-                .processedWith(LifecycleProcessor())
-                .compilesWithoutError().and()
-                .generatesSources(load("foo.DerivedFromJar_LifecycleAdapter", "expected"))
-    }
-
-    @Test
-    fun testExtendFromJarFailToGenerateAdapter() {
-        val compileTester = JavaSourcesSubject.assertThat(load("foo.DerivedFromJar1", ""))
-                .withClasspathFrom(libraryClassLoader())
-                .processedWith(LifecycleProcessor())
-                .compilesWithoutError()
-        compileTester.withWarningContaining("Failed to generate an Adapter for")
-        doesntGenerateClass(compileTester, "test.library", "ObserverNoAdapter_LifecycleAdapter")
-        doesntGenerateClass(compileTester, "foo", "DerivedFromJar1_LifecycleAdapter")
-    }
-
-    // compile-testing has fancy, but not always convenient API
-    private fun doesntGenerateClass(compile: CompileTester.SuccessfulCompilationClause,
-                                    packageName: String, className: String) {
-        try {
-            compile.and().generatesFileNamed(StandardLocation.CLASS_OUTPUT,
-                    packageName, "$className.class")
-            throw Exception("$packageName.$className shouldn't be generated")
-        } catch (e: AssertionError) {
-        }
-    }
-
-    private fun libraryClassLoader(): URLClassLoader {
-        val jarUrl = File("src/tests/test-data/lib/test-library.jar").toURI().toURL()
-        return URLClassLoader(arrayOf(jarUrl), this.javaClass.classLoader)
-    }
-}
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/utils/TestUtils.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/utils/TestUtils.kt
deleted file mode 100644
index d3b13c2..0000000
--- a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/utils/TestUtils.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.utils
-
-import android.arch.lifecycle.LifecycleProcessor
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourceSubjectFactory
-import com.google.testing.compile.JavaSourcesSubject
-import java.io.File
-import java.nio.charset.Charset
-import javax.tools.JavaFileObject
-
-fun load(fullClassName: String, folder: String): JavaFileObject {
-    val folderPath = "src/tests/test-data/${if (folder.isEmpty()) "" else folder + "/"}"
-    val split = fullClassName.split(".")
-    val code = File("$folderPath/${split.last()}.java").readText(Charset.defaultCharset())
-    return JavaFileObjects.forSourceString(fullClassName, code)
-}
-
-fun processClass(className: String, vararg fullClassNames: String): CompileTester {
-    val javaFiles = fullClassNames.map { load(it, "") }.toTypedArray()
-    val processedWith = JavaSourcesSubject.assertThat(load(className, ""), *javaFiles)
-            .processedWith(LifecycleProcessor())
-    return checkNotNull(processedWith)
-}
diff --git a/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/InvalidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/InvalidCasesTest.kt
new file mode 100644
index 0000000..6a1f114
--- /dev/null
+++ b/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/InvalidCasesTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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 androidx.lifecycle
+
+import androidx.lifecycle.utils.processClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class InvalidCasesTest(val name: String, val errorMsg: String) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "failingCase({0})")
+        fun data(): Collection<Array<Any>> = listOf(
+                arrayOf<Any>("foo.InvalidFirstArg1", ErrorMessages.INVALID_FIRST_ARGUMENT),
+                arrayOf<Any>("foo.InvalidFirstArg2", ErrorMessages.INVALID_FIRST_ARGUMENT),
+                arrayOf<Any>("foo.InvalidSecondArg", ErrorMessages.INVALID_SECOND_ARGUMENT),
+                arrayOf<Any>("foo.TooManyArgs1", ErrorMessages.TOO_MANY_ARGS),
+                arrayOf<Any>("foo.TooManyArgs2", ErrorMessages.TOO_MANY_ARGS_NOT_ON_ANY),
+                arrayOf<Any>("foo.InvalidMethodModifier",
+                        ErrorMessages.INVALID_METHOD_MODIFIER),
+                arrayOf<Any>("foo.InvalidClassModifier", ErrorMessages.INVALID_CLASS_MODIFIER),
+                arrayOf<Any>("foo.InvalidInheritance1",
+                        ErrorMessages.INVALID_STATE_OVERRIDE_METHOD),
+                arrayOf<Any>("foo.InvalidInheritance2",
+                        ErrorMessages.INVALID_STATE_OVERRIDE_METHOD)
+        )
+    }
+
+    @Test
+    fun shouldFailWithError() {
+        processClass(name).failsToCompile().withErrorContaining(errorMsg)
+    }
+}
diff --git a/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/ValidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/ValidCasesTest.kt
new file mode 100644
index 0000000..f6139a0
--- /dev/null
+++ b/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/ValidCasesTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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 androidx.lifecycle
+
+import androidx.lifecycle.utils.load
+import androidx.lifecycle.utils.processClass
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaSourcesSubject
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.io.File
+import java.lang.Exception
+import java.net.URLClassLoader
+import javax.tools.StandardLocation
+
+@RunWith(JUnit4::class)
+class ValidCasesTest {
+    @Test
+    fun testTest() {
+        processClass("foo.Bar").compilesWithoutError()
+    }
+
+    @Test
+    fun testOnAny() {
+        processClass("foo.OnAnyMethod").compilesWithoutError().and().generatesSources(
+                load("foo.OnAnyMethod_LifecycleAdapter", "expected")
+        ).and().generatesProGuardRule("foo.OnAnyMethod.pro")
+    }
+
+    @Test
+    fun testInheritance() {
+        processClass("foo.InheritanceOk1").compilesWithoutError()
+    }
+
+    @Test
+    fun testInheritance2() {
+        processClass("foo.InheritanceOk2").compilesWithoutError().and().generatesSources(
+                load("foo.InheritanceOk2Base_LifecycleAdapter", "expected"),
+                load("foo.InheritanceOk2Derived_LifecycleAdapter", "expected")
+        )
+                .and().generatesProGuardRule("foo.InheritanceOk2Base.pro")
+                .and().generatesProGuardRule("foo.InheritanceOk2Derived.pro")
+    }
+
+    @Test
+    fun testInheritance3() {
+        processClass("foo.InheritanceOk3").compilesWithoutError().and().generatesSources(
+                load("foo.InheritanceOk3Base_LifecycleAdapter", "expected"),
+                load("foo.InheritanceOk3Derived_LifecycleAdapter", "expected")
+        )
+                .and().generatesProGuardRule("foo.InheritanceOk3Base.pro")
+                .and().generatesProGuardRule("foo.InheritanceOk3Derived.pro")
+    }
+
+    @Test
+    fun testNoPackageClass() {
+        processClass("NoPackageOk").compilesWithoutError()
+    }
+
+    @Test
+    fun testInterface1() {
+        processClass("foo.InterfaceOk1").compilesWithoutError()
+    }
+
+    @Test
+    fun testInterface2() {
+        processClass("foo.InterfaceOk2").compilesWithoutError().and().generatesSources(
+                load("foo.InterfaceOk2Base_LifecycleAdapter", "expected"),
+                load("foo.InterfaceOk2Derived_LifecycleAdapter", "expected"),
+                load("foo.InterfaceOk2Interface_LifecycleAdapter", "expected")
+        )
+                .and().generatesProGuardRule("foo.InterfaceOk2Base.pro")
+                .and().generatesProGuardRule("foo.InterfaceOk2Derived.pro")
+                .and().generatesProGuardRule("foo.InterfaceOk2Interface.pro")
+    }
+
+    @Test
+    fun testInheritanceDifferentPackages1() {
+        processClass("foo.DifferentPackagesBase1",
+                "bar.DifferentPackagesDerived1").compilesWithoutError().and().generatesSources(
+                load("foo.DifferentPackagesBase1_LifecycleAdapter", "expected"),
+                load("bar.DifferentPackagesDerived1_LifecycleAdapter", "expected")
+        )
+                .and().generatesProGuardRule("foo.DifferentPackagesBase1.pro")
+                .and().generatesProGuardRule("bar.DifferentPackagesDerived1.pro")
+    }
+
+    @Test
+    fun testInheritanceDifferentPackages2() {
+        processClass("foo.DifferentPackagesBase2",
+                "bar.DifferentPackagesDerived2").compilesWithoutError().and().generatesSources(
+                load("foo.DifferentPackagesBase2_LifecycleAdapter", "expected"),
+                load("bar.DifferentPackagesDerived2_LifecycleAdapter", "expected")
+        )
+                .and().generatesProGuardRule("foo.DifferentPackagesPreBase2.pro")
+                .and().generatesProGuardRule("foo.DifferentPackagesBase2.pro")
+                .and().generatesProGuardRule("bar.DifferentPackagesDerived2.pro")
+    }
+
+    private fun <T> CompileTester.GeneratedPredicateClause<T>.generatesProGuardRule(name: String):
+            CompileTester.SuccessfulFileClause<T> {
+        return generatesFileNamed(StandardLocation.CLASS_OUTPUT, "", "META-INF/proguard/$name")
+    }
+
+    @Test
+    fun testJar() {
+        JavaSourcesSubject.assertThat(load("foo.DerivedFromJar", ""))
+                .withClasspathFrom(libraryClassLoader())
+                .processedWith(LifecycleProcessor())
+                .compilesWithoutError().and()
+                .generatesSources(load("foo.DerivedFromJar_LifecycleAdapter", "expected"))
+    }
+
+    @Test
+    fun testExtendFromJarFailToGenerateAdapter() {
+        val compileTester = JavaSourcesSubject.assertThat(load("foo.DerivedFromJar1", ""))
+                .withClasspathFrom(libraryClassLoader())
+                .processedWith(LifecycleProcessor())
+                .compilesWithoutError()
+        compileTester.withWarningContaining("Failed to generate an Adapter for")
+        doesntGenerateClass(compileTester, "test.library", "ObserverNoAdapter_LifecycleAdapter")
+        doesntGenerateClass(compileTester, "foo", "DerivedFromJar1_LifecycleAdapter")
+    }
+
+    // compile-testing has fancy, but not always convenient API
+    private fun doesntGenerateClass(compile: CompileTester.SuccessfulCompilationClause,
+                                    packageName: String, className: String) {
+        try {
+            compile.and().generatesFileNamed(StandardLocation.CLASS_OUTPUT,
+                    packageName, "$className.class")
+            throw Exception("$packageName.$className shouldn't be generated")
+        } catch (e: AssertionError) {
+        }
+    }
+
+    private fun libraryClassLoader(): URLClassLoader {
+        val jarUrl = File("src/tests/test-data/lib/test-library.jar").toURI().toURL()
+        return URLClassLoader(arrayOf(jarUrl), this.javaClass.classLoader)
+    }
+}
diff --git a/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/utils/TestUtils.kt b/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/utils/TestUtils.kt
new file mode 100644
index 0000000..b29a2f0
--- /dev/null
+++ b/lifecycle/compiler/src/tests/kotlin/androidx/lifecycle/utils/TestUtils.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.utils
+
+import androidx.lifecycle.LifecycleProcessor
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubject
+import java.io.File
+import java.nio.charset.Charset
+import javax.tools.JavaFileObject
+
+fun load(fullClassName: String, folder: String): JavaFileObject {
+    val folderPath = "src/tests/test-data/${if (folder.isEmpty()) "" else folder + "/"}"
+    val split = fullClassName.split(".")
+    val code = File("$folderPath/${split.last()}.java").readText(Charset.defaultCharset())
+    return JavaFileObjects.forSourceString(fullClassName, code)
+}
+
+fun processClass(className: String, vararg fullClassNames: String): CompileTester {
+    val javaFiles = fullClassNames.map { load(it, "") }.toTypedArray()
+    val processedWith = JavaSourcesSubject.assertThat(load(className, ""), *javaFiles)
+            .processedWith(LifecycleProcessor())
+    return checkNotNull(processedWith)
+}
diff --git a/lifecycle/compiler/src/tests/test-data/Bar.java b/lifecycle/compiler/src/tests/test-data/Bar.java
index cfc41ca..64c7afc 100644
--- a/lifecycle/compiler/src/tests/test-data/Bar.java
+++ b/lifecycle/compiler/src/tests/test-data/Bar.java
@@ -1,12 +1,12 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class Bar implements LifecycleObserver {
     @OnLifecycleEvent(ON_START)
diff --git a/lifecycle/compiler/src/tests/test-data/DerivedFromJar.java b/lifecycle/compiler/src/tests/test-data/DerivedFromJar.java
index d503903..834925d 100644
--- a/lifecycle/compiler/src/tests/test-data/DerivedFromJar.java
+++ b/lifecycle/compiler/src/tests/test-data/DerivedFromJar.java
@@ -1,9 +1,9 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class DerivedFromJar extends test.library.LibraryBaseObserver {
     @OnLifecycleEvent(ON_START)
diff --git a/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java b/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
index efcfaff..f669761 100644
--- a/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
+++ b/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
@@ -16,10 +16,10 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class DerivedFromJar1 extends test.library.PPObserverNoAdapter {
     @OnLifecycleEvent(ON_START)
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase1.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase1.java
index fb0966d..82c0699 100644
--- a/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase1.java
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase1.java
@@ -16,12 +16,12 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class DifferentPackagesBase1 implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase2.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase2.java
index 1c3ea72..8343a1c 100644
--- a/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase2.java
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesBase2.java
@@ -16,12 +16,12 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class DifferentPackagesPreBase2 implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived1.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived1.java
index 8ad4c91..5c218b5 100644
--- a/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived1.java
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived1.java
@@ -16,11 +16,11 @@
 
 package bar;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 import foo.DifferentPackagesBase1;
 
 public class DifferentPackagesDerived1 extends DifferentPackagesBase1 {
diff --git a/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived2.java b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived2.java
index 648f628..a75f227 100644
--- a/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived2.java
+++ b/lifecycle/compiler/src/tests/test-data/DifferentPackagesDerived2.java
@@ -16,11 +16,11 @@
 
 package bar;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 import foo.DifferentPackagesBase2;
 
 public class DifferentPackagesDerived2 extends DifferentPackagesBase2 {
diff --git a/lifecycle/compiler/src/tests/test-data/InheritanceOk1.java b/lifecycle/compiler/src/tests/test-data/InheritanceOk1.java
index 889c463..d111439 100644
--- a/lifecycle/compiler/src/tests/test-data/InheritanceOk1.java
+++ b/lifecycle/compiler/src/tests/test-data/InheritanceOk1.java
@@ -16,12 +16,12 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class Base1 implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InheritanceOk2.java b/lifecycle/compiler/src/tests/test-data/InheritanceOk2.java
index bb00cca..ade9a86 100644
--- a/lifecycle/compiler/src/tests/test-data/InheritanceOk2.java
+++ b/lifecycle/compiler/src/tests/test-data/InheritanceOk2.java
@@ -1,11 +1,11 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class InheritanceOk2Base implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InheritanceOk3.java b/lifecycle/compiler/src/tests/test-data/InheritanceOk3.java
index 5a3c91d..b429848 100644
--- a/lifecycle/compiler/src/tests/test-data/InheritanceOk3.java
+++ b/lifecycle/compiler/src/tests/test-data/InheritanceOk3.java
@@ -16,12 +16,12 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class InheritanceOk3Base implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InterfaceOk1.java b/lifecycle/compiler/src/tests/test-data/InterfaceOk1.java
index 8450302..d80e6d9 100644
--- a/lifecycle/compiler/src/tests/test-data/InterfaceOk1.java
+++ b/lifecycle/compiler/src/tests/test-data/InterfaceOk1.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 interface InterfaceOk1 extends LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InterfaceOk2.java b/lifecycle/compiler/src/tests/test-data/InterfaceOk2.java
index fee5b10..5fc2b92 100644
--- a/lifecycle/compiler/src/tests/test-data/InterfaceOk2.java
+++ b/lifecycle/compiler/src/tests/test-data/InterfaceOk2.java
@@ -16,12 +16,12 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class InterfaceOk2Base implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidClassModifier.java b/lifecycle/compiler/src/tests/test-data/InvalidClassModifier.java
index 919bb8c..c93fe13 100644
--- a/lifecycle/compiler/src/tests/test-data/InvalidClassModifier.java
+++ b/lifecycle/compiler/src/tests/test-data/InvalidClassModifier.java
@@ -16,11 +16,11 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class InvalidClassModifier {
     private static class Inner implements LifecycleObserver {
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidFirstArg1.java b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg1.java
index b028443..2ecb0ec 100644
--- a/lifecycle/compiler/src/tests/test-data/InvalidFirstArg1.java
+++ b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg1.java
@@ -1,10 +1,10 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class InvalidFirstArg1 implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidFirstArg2.java b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg2.java
index d9937ac..3d7b3bf 100644
--- a/lifecycle/compiler/src/tests/test-data/InvalidFirstArg2.java
+++ b/lifecycle/compiler/src/tests/test-data/InvalidFirstArg2.java
@@ -1,10 +1,10 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+import static androidx.lifecycle.Lifecycle.Event.ON_ANY;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class InvalidFirstArg2 implements LifecycleObserver {
     @OnLifecycleEvent(ON_ANY)
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidInheritance1.java b/lifecycle/compiler/src/tests/test-data/InvalidInheritance1.java
index d8f0f62..e0c099a 100644
--- a/lifecycle/compiler/src/tests/test-data/InvalidInheritance1.java
+++ b/lifecycle/compiler/src/tests/test-data/InvalidInheritance1.java
@@ -16,11 +16,11 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class Base implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidInheritance2.java b/lifecycle/compiler/src/tests/test-data/InvalidInheritance2.java
index b63ad44..c3c8cac 100644
--- a/lifecycle/compiler/src/tests/test-data/InvalidInheritance2.java
+++ b/lifecycle/compiler/src/tests/test-data/InvalidInheritance2.java
@@ -16,11 +16,11 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 interface Base extends LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidMethodModifier.java b/lifecycle/compiler/src/tests/test-data/InvalidMethodModifier.java
index 5eed7b8..00d817d 100644
--- a/lifecycle/compiler/src/tests/test-data/InvalidMethodModifier.java
+++ b/lifecycle/compiler/src/tests/test-data/InvalidMethodModifier.java
@@ -16,11 +16,11 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class InvalidMethodModifier implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/InvalidSecondArg.java b/lifecycle/compiler/src/tests/test-data/InvalidSecondArg.java
index 60ad464..07df549 100644
--- a/lifecycle/compiler/src/tests/test-data/InvalidSecondArg.java
+++ b/lifecycle/compiler/src/tests/test-data/InvalidSecondArg.java
@@ -1,10 +1,10 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+import static androidx.lifecycle.Lifecycle.Event.ON_ANY;
 
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class InvalidSecondArg implements LifecycleObserver {
     @OnLifecycleEvent(ON_ANY)
diff --git a/lifecycle/compiler/src/tests/test-data/NoPackageOk.java b/lifecycle/compiler/src/tests/test-data/NoPackageOk.java
index e7a84ef..e972151 100644
--- a/lifecycle/compiler/src/tests/test-data/NoPackageOk.java
+++ b/lifecycle/compiler/src/tests/test-data/NoPackageOk.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class NoPackageOk implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/OnAnyMethod.java b/lifecycle/compiler/src/tests/test-data/OnAnyMethod.java
index 8761774..22b8c07 100644
--- a/lifecycle/compiler/src/tests/test-data/OnAnyMethod.java
+++ b/lifecycle/compiler/src/tests/test-data/OnAnyMethod.java
@@ -16,13 +16,13 @@
 
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_ANY;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class OnAnyMethod implements LifecycleObserver {
 
diff --git a/lifecycle/compiler/src/tests/test-data/TooManyArgs1.java b/lifecycle/compiler/src/tests/test-data/TooManyArgs1.java
index cdcff60..4599ecd 100644
--- a/lifecycle/compiler/src/tests/test-data/TooManyArgs1.java
+++ b/lifecycle/compiler/src/tests/test-data/TooManyArgs1.java
@@ -1,11 +1,11 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+import static androidx.lifecycle.Lifecycle.Event.ON_ANY;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class TooManyArgs1 implements LifecycleObserver {
     @OnLifecycleEvent(ON_ANY)
diff --git a/lifecycle/compiler/src/tests/test-data/TooManyArgs2.java b/lifecycle/compiler/src/tests/test-data/TooManyArgs2.java
index b38d906..b4fe75b 100644
--- a/lifecycle/compiler/src/tests/test-data/TooManyArgs2.java
+++ b/lifecycle/compiler/src/tests/test-data/TooManyArgs2.java
@@ -1,11 +1,11 @@
 package foo;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class TooManyArgs2 implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java
index 2ff2db2..99642cf 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class DerivedFromJar1_LifecycleAdapter implements GeneratedAdapter {
     final DerivedFromJar1 mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar_LifecycleAdapter.java
index 191499d..d506d6d 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar_LifecycleAdapter.java
@@ -16,15 +16,15 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 import test.library.LibraryBaseObserver_LifecycleAdapter;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class DerivedFromJar_LifecycleAdapter implements GeneratedAdapter {
   final DerivedFromJar mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase1_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase1_LifecycleAdapter.java
index 9b9c9ea..b29c24e 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase1_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase1_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class DifferentPackagesBase1_LifecycleAdapter implements GeneratedAdapter {
   final DifferentPackagesBase1 mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase2_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase2_LifecycleAdapter.java
index d98b5e4..13bb302 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase2_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesBase2_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class DifferentPackagesBase2_LifecycleAdapter implements GeneratedAdapter {
   final DifferentPackagesBase2 mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived1_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived1_LifecycleAdapter.java
index 4ff134f..eacb58a 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived1_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived1_LifecycleAdapter.java
@@ -16,15 +16,15 @@
 
 package bar;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import foo.DifferentPackagesBase1_LifecycleAdapter;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class DifferentPackagesDerived1_LifecycleAdapter implements GeneratedAdapter {
   final DifferentPackagesDerived1 mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived2_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived2_LifecycleAdapter.java
index 4132f95..5dc1d9b 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived2_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/DifferentPackagesDerived2_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package bar;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class DifferentPackagesDerived2_LifecycleAdapter implements GeneratedAdapter {
   final DifferentPackagesDerived2 mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Base_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Base_LifecycleAdapter.java
index 2013e46..25a6353 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Base_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Base_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class InheritanceOk2Base_LifecycleAdapter implements GeneratedAdapter {
   final InheritanceOk2Base mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Derived_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Derived_LifecycleAdapter.java
index 62a631f..a673cc3 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Derived_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk2Derived_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class InheritanceOk2Derived_LifecycleAdapter implements GeneratedAdapter {
   final InheritanceOk2Derived mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Base_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Base_LifecycleAdapter.java
index 4b545d9..d7ac84b 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Base_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Base_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class InheritanceOk3Base_LifecycleAdapter implements GeneratedAdapter {
   final InheritanceOk3Base mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Derived_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Derived_LifecycleAdapter.java
index 643617c..ab5d512 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Derived_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/InheritanceOk3Derived_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class InheritanceOk3Derived_LifecycleAdapter implements GeneratedAdapter {
   final InheritanceOk3Derived mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Base_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Base_LifecycleAdapter.java
index 59aa5a2..bc580ca 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Base_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Base_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class InterfaceOk2Base_LifecycleAdapter implements GeneratedAdapter {
   final InterfaceOk2Base mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Derived_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Derived_LifecycleAdapter.java
index e04dec6..04d13e3 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Derived_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Derived_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class InterfaceOk2Derived_LifecycleAdapter implements GeneratedAdapter {
   final InterfaceOk2Derived mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Interface_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Interface_LifecycleAdapter.java
index 268f68b..dab1a1a 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Interface_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/InterfaceOk2Interface_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class InterfaceOk2Interface_LifecycleAdapter implements GeneratedAdapter {
   final InterfaceOk2Interface mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java
index c2dc1b6..0956a80 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package test.library;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class ObserverNoAdapter_LifecycleAdapter implements GeneratedAdapter {
     final ObserverNoAdapter mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/expected/OnAnyMethod_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/OnAnyMethod_LifecycleAdapter.java
index ba7ccc1..39a1768 100644
--- a/lifecycle/compiler/src/tests/test-data/expected/OnAnyMethod_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/expected/OnAnyMethod_LifecycleAdapter.java
@@ -16,14 +16,14 @@
 
 package foo;
 
-import android.arch.lifecycle.GeneratedAdapter;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MethodCallsLogger;
+import androidx.lifecycle.GeneratedAdapter;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MethodCallsLogger;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class OnAnyMethod_LifecycleAdapter implements GeneratedAdapter {
   final OnAnyMethod mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver.java
index c3e83e7..94d3643 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver.java
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver.java
@@ -16,13 +16,13 @@
 
 package test.library;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class LibraryBaseObserver implements LifecycleObserver {
     @OnLifecycleEvent(ON_START)
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver_LifecycleAdapter.java
index ea7e631..5412e29 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver_LifecycleAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/LibraryBaseObserver_LifecycleAdapter.java
@@ -16,13 +16,13 @@
 
 package test.library;
 
-import android.arch.lifecycle.GenericLifecycleObserver;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
+import androidx.lifecycle.GenericLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
 import java.lang.Override;
 import javax.annotation.Generated;
 
-@Generated("android.arch.lifecycle.LifecycleProcessor")
+@Generated("androidx.lifecycle.LifecycleProcessor")
 public class LibraryBaseObserver_LifecycleAdapter implements GenericLifecycleObserver {
     final LibraryBaseObserver mReceiver;
 
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
index ea000f7..75004ac 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
@@ -16,12 +16,12 @@
 
 package test.library;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class ObserverNoAdapter implements LifecycleObserver {
     @OnLifecycleEvent(ON_STOP)
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java
index 06e10b5..382e3d8 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java
@@ -16,13 +16,13 @@
 
 package test.library;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class PPObserverNoAdapter implements LifecycleObserver {
     @OnLifecycleEvent(ON_START)
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java
index 25a9b5d..828e9c0 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java
@@ -16,13 +16,13 @@
 
 package test.library;
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
 
 public class PPObserverWithAdapter implements LifecycleObserver {
     @OnLifecycleEvent(ON_START)
diff --git a/lifecycle/compiler/src/tests/test-data/lib/test-library.jar b/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
index cd9ac8b..587dbd5 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
+++ b/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
Binary files differ
diff --git a/lifecycle/extensions/api/current.txt b/lifecycle/extensions/api/current.txt
index 8bd7d59..dad4ab8 100644
--- a/lifecycle/extensions/api/current.txt
+++ b/lifecycle/extensions/api/current.txt
@@ -1,20 +1,20 @@
-package android.arch.lifecycle {
+package androidx.lifecycle {
 
-  public class LifecycleService extends android.app.Service implements android.arch.lifecycle.LifecycleOwner {
+  public class LifecycleService extends android.app.Service implements androidx.lifecycle.LifecycleOwner {
     ctor public LifecycleService();
-    method public android.arch.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onStart(android.content.Intent, int);
   }
 
-  public class ProcessLifecycleOwner implements android.arch.lifecycle.LifecycleOwner {
-    method public static android.arch.lifecycle.LifecycleOwner get();
-    method public android.arch.lifecycle.Lifecycle getLifecycle();
+  public class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
+    method public static androidx.lifecycle.LifecycleOwner get();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
   }
 
   public class ServiceLifecycleDispatcher {
-    ctor public ServiceLifecycleDispatcher(android.arch.lifecycle.LifecycleOwner);
-    method public android.arch.lifecycle.Lifecycle getLifecycle();
+    ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner);
+    method public androidx.lifecycle.Lifecycle getLifecycle();
     method public void onServicePreSuperOnBind();
     method public void onServicePreSuperOnCreate();
     method public void onServicePreSuperOnDestroy();
@@ -23,19 +23,19 @@
 
   public class ViewModelProviders {
     ctor public deprecated ViewModelProviders();
-    method public static android.arch.lifecycle.ViewModelProvider of(android.support.v4.app.Fragment);
-    method public static android.arch.lifecycle.ViewModelProvider of(android.support.v4.app.FragmentActivity);
-    method public static android.arch.lifecycle.ViewModelProvider of(android.support.v4.app.Fragment, android.arch.lifecycle.ViewModelProvider.Factory);
-    method public static android.arch.lifecycle.ViewModelProvider of(android.support.v4.app.FragmentActivity, android.arch.lifecycle.ViewModelProvider.Factory);
+    method public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
+    method public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
+    method public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory);
+    method public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory);
   }
 
-  public static deprecated class ViewModelProviders.DefaultFactory extends android.arch.lifecycle.ViewModelProvider.AndroidViewModelFactory {
+  public static deprecated class ViewModelProviders.DefaultFactory extends androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory {
     ctor public deprecated ViewModelProviders.DefaultFactory(android.app.Application);
   }
 
   public class ViewModelStores {
-    method public static android.arch.lifecycle.ViewModelStore of(android.support.v4.app.FragmentActivity);
-    method public static android.arch.lifecycle.ViewModelStore of(android.support.v4.app.Fragment);
+    method public static androidx.lifecycle.ViewModelStore of(androidx.fragment.app.FragmentActivity);
+    method public static androidx.lifecycle.ViewModelStore of(androidx.fragment.app.Fragment);
   }
 
 }
diff --git a/lifecycle/extensions/api/1.0.0.ignore b/lifecycle/extensions/api_legacy/1.0.0.ignore
similarity index 100%
rename from lifecycle/extensions/api/1.0.0.ignore
rename to lifecycle/extensions/api_legacy/1.0.0.ignore
diff --git a/lifecycle/extensions/api/1.0.0.txt b/lifecycle/extensions/api_legacy/1.0.0.txt
similarity index 100%
rename from lifecycle/extensions/api/1.0.0.txt
rename to lifecycle/extensions/api_legacy/1.0.0.txt
diff --git a/lifecycle/extensions/api/1.1.0.txt b/lifecycle/extensions/api_legacy/1.1.0.txt
similarity index 100%
rename from lifecycle/extensions/api/1.1.0.txt
rename to lifecycle/extensions/api_legacy/1.1.0.txt
diff --git a/lifecycle/extensions/api/1.1.0.txt b/lifecycle/extensions/api_legacy/current.txt
similarity index 100%
copy from lifecycle/extensions/api/1.1.0.txt
copy to lifecycle/extensions/api_legacy/current.txt
diff --git a/lifecycle/extensions/build.gradle b/lifecycle/extensions/build.gradle
index 80b7fc8..a2be25d 100644
--- a/lifecycle/extensions/build.gradle
+++ b/lifecycle/extensions/build.gradle
@@ -30,13 +30,13 @@
 }
 
 dependencies {
-    api(project(":lifecycle:runtime"))
-    api(project(":arch:common"))
-    api(project(":arch:runtime"))
+    api(project(":lifecycle:lifecycle-runtime"))
+    api(project(":arch:core-common"))
+    api(project(":arch:core-runtime"))
     api(SUPPORT_FRAGMENTS, libs.support_exclude_config)
-    api(project(":lifecycle:common"))
-    api(project(":lifecycle:livedata"))
-    api(project(":lifecycle:viewmodel"))
+    api(project(":lifecycle:lifecycle-common"))
+    api(project(":lifecycle:lifecycle-livedata"))
+    api(project(":lifecycle:lifecycle-viewmodel"))
 
     testImplementation(project(":arch:core-testing"))
     testImplementation(JUNIT)
diff --git a/lifecycle/extensions/proguard-rules.pro b/lifecycle/extensions/proguard-rules.pro
index 68db05a..c2d2146 100644
--- a/lifecycle/extensions/proguard-rules.pro
+++ b/lifecycle/extensions/proguard-rules.pro
@@ -1,3 +1,3 @@
--keep class * extends android.arch.lifecycle.AndroidViewModel {
+-keep class * extends androidx.lifecycle.AndroidViewModel {
     <init>(android.app.Application);
 }
\ No newline at end of file
diff --git a/lifecycle/extensions/src/androidTest/AndroidManifest.xml b/lifecycle/extensions/src/androidTest/AndroidManifest.xml
index db921fd..d0f5483 100644
--- a/lifecycle/extensions/src/androidTest/AndroidManifest.xml
+++ b/lifecycle/extensions/src/androidTest/AndroidManifest.xml
@@ -16,18 +16,18 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle.extensions.test">
+          package="androidx.lifecycle.extensions.test">
     <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
 
     <application>
-        <activity android:name="android.arch.lifecycle.viewmodeltest.ViewModelActivity"
+        <activity android:name="androidx.lifecycle.viewmodeltest.ViewModelActivity"
                   android:theme="@style/Base.Theme.AppCompat">
         </activity>
-        <activity android:name="android.arch.lifecycle.EmptyActivity"/>
-        <activity android:name="android.arch.lifecycle.activity.FragmentLifecycleActivity"
+        <activity android:name="androidx.lifecycle.EmptyActivity"/>
+        <activity android:name="androidx.lifecycle.activity.FragmentLifecycleActivity"
             android:theme="@style/Base.Theme.AppCompat"/>
-        <activity android:name="android.arch.lifecycle.activity.EmptyActivity"/>
-        <service android:name="android.arch.lifecycle.service.TestService"/>
+        <activity android:name="androidx.lifecycle.activity.EmptyActivity"/>
+        <service android:name="androidx.lifecycle.service.TestService"/>
     </application>
 
 </manifest>
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/EmptyActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/EmptyActivity.java
deleted file mode 100644
index 738bd66..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/EmptyActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.v4.app.FragmentActivity;
-
-public class EmptyActivity extends FragmentActivity {
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentLifecycleInActivityTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentLifecycleInActivityTest.java
deleted file mode 100644
index 70a4465..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentLifecycleInActivityTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.app.Instrumentation;
-import android.arch.lifecycle.activity.FragmentLifecycleActivity;
-import android.content.Intent;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v4.app.Fragment;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-@MediumTest
-@RunWith(Parameterized.class)
-public class FragmentLifecycleInActivityTest {
-
-    private static final long TIMEOUT = 2; //sec
-
-    @Rule
-    public ActivityTestRule<FragmentLifecycleActivity> mActivityRule =
-            new ActivityTestRule<>(FragmentLifecycleActivity.class, false, false);
-
-    private Instrumentation mInstrumentation;
-
-    @SuppressWarnings("WeakerAccess")
-    @Parameterized.Parameter
-    public boolean mNested;
-
-    @Parameterized.Parameters(name = "nested_{0}")
-    public static Object[][] params() {
-        return new Object[][]{new Object[]{false}, new Object[]{true}};
-    }
-
-    @Before
-    public void getInstrumentation() {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-    }
-
-    private void reset() {
-        mActivityRule.getActivity().resetEvents();
-    }
-
-    @Test
-    public void testFullEvents() throws Throwable {
-        final FragmentLifecycleActivity activity = launchActivity();
-        waitForIdle();
-        assertEvents(ON_CREATE, ON_START, ON_RESUME);
-        reset();
-        finishActivity(activity);
-        assertEvents(ON_PAUSE, ON_STOP, ON_DESTROY);
-    }
-
-    @Test
-    public void testStopStart() throws Throwable {
-        final FragmentLifecycleActivity activity = launchActivity();
-        waitForIdle();
-        assertEvents(ON_CREATE, ON_START, ON_RESUME);
-        reset();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mInstrumentation.callActivityOnPause(activity);
-                mInstrumentation.callActivityOnStop(activity);
-            }
-        });
-        waitForIdle();
-        assertEvents(ON_PAUSE, ON_STOP);
-        reset();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mInstrumentation.callActivityOnStart(activity);
-                mInstrumentation.callActivityOnResume(activity);
-            }
-        });
-        waitForIdle();
-        assertEvents(ON_START, ON_RESUME);
-    }
-
-    private FragmentLifecycleActivity launchActivity() throws Throwable {
-        Intent intent = FragmentLifecycleActivity.intentFor(mInstrumentation.getTargetContext(),
-                mNested);
-        final FragmentLifecycleActivity activity = mActivityRule.launchActivity(intent);
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Fragment main = activity.getSupportFragmentManager()
-                        .findFragmentByTag(FragmentLifecycleActivity.MAIN_TAG);
-                assertThat("test sanity", main, notNullValue());
-                Fragment nestedFragment = main.getChildFragmentManager()
-                        .findFragmentByTag(FragmentLifecycleActivity.NESTED_TAG);
-                assertThat("test sanity", nestedFragment != null, is(mNested));
-            }
-        });
-        assertThat(activity.getObservedOwner(), instanceOf(
-                mNested ? FragmentLifecycleActivity.NestedFragment.class
-                        : FragmentLifecycleActivity.MainFragment.class
-        ));
-        return activity;
-    }
-
-    private void waitForIdle() {
-        mInstrumentation.waitForIdleSync();
-    }
-
-    private void finishActivity(final FragmentLifecycleActivity activity)
-            throws InterruptedException {
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                activity.finish();
-            }
-        });
-        assertThat(activity.awaitForDestruction(TIMEOUT, TimeUnit.SECONDS), is(true));
-    }
-
-    private void assertEvents(Lifecycle.Event... events) {
-        assertThat(mActivityRule.getActivity().getLoggedEvents(), is(Arrays.asList(events)));
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentOperationsLifecycleTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
deleted file mode 100644
index 3e61277..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-import static java.util.Arrays.asList;
-
-import android.arch.lifecycle.activity.EmptyActivity;
-import android.arch.lifecycle.extensions.test.R;
-import android.support.test.annotation.UiThreadTest;
-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;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentOperationsLifecycleTest {
-
-    @Rule
-    public ActivityTestRule<EmptyActivity> mActivityTestRule = new ActivityTestRule<>(
-            EmptyActivity.class);
-
-    @Test
-    @UiThreadTest
-    public void addRemoveFragment() {
-        EmptyActivity activity = mActivityTestRule.getActivity();
-        Fragment fragment = new Fragment();
-        FragmentManager fm = activity.getSupportFragmentManager();
-        fm.beginTransaction().add(fragment, "tag").commitNow();
-        CollectingObserver observer = observeAndCollectIn(fragment);
-        assertThat(observer.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
-        fm.beginTransaction().remove(fragment).commitNow();
-        assertThat(observer.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
-        fm.beginTransaction().add(fragment, "tag").commitNow();
-        assertThat(observer.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
-    }
-
-    @Test
-    @UiThreadTest
-    public void fragmentInBackstack() {
-        EmptyActivity activity = mActivityTestRule.getActivity();
-        Fragment fragment1 = new Fragment();
-        FragmentManager fm = activity.getSupportFragmentManager();
-        fm.beginTransaction().add(R.id.fragment_container, fragment1, "tag").addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-        CollectingObserver observer1 = observeAndCollectIn(fragment1);
-        assertThat(observer1.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
-
-        Fragment fragment2 = new Fragment();
-        fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-
-        CollectingObserver observer2 = observeAndCollectIn(fragment2);
-        assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP)));
-        assertThat(observer2.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
-
-        assertThat(fm.popBackStackImmediate(), is(true));
-        assertThat(observer1.getEventsAndReset(), is(asList(ON_START, ON_RESUME)));
-        assertThat(observer2.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
-
-        assertThat(fm.popBackStackImmediate(), is(true));
-        assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
-    }
-
-    private static CollectingObserver observeAndCollectIn(Fragment fragment) {
-        CollectingObserver observer = new CollectingObserver();
-        fragment.getLifecycle().addObserver(observer);
-        return observer;
-    }
-
-    private static class CollectingObserver implements LifecycleObserver {
-        final List<Lifecycle.Event> mCollectedEvents = new ArrayList<>();
-
-        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-        public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
-            mCollectedEvents.add(event);
-        }
-
-        List<Lifecycle.Event> getEventsAndReset() {
-            ArrayList<Lifecycle.Event> events = new ArrayList<>(mCollectedEvents);
-            mCollectedEvents.clear();
-            return events;
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ServiceLifecycleTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ServiceLifecycleTest.java
deleted file mode 100644
index fe0a306..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ServiceLifecycleTest.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.service.TestService;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v4.content.LocalBroadcastManager;
-
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ServiceLifecycleTest {
-
-    private static final int RETRY_NUMBER = 5;
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(1);
-
-    private Intent mServiceIntent;
-
-    private volatile List<Event> mLoggerEvents;
-    private EventLogger mLogger;
-
-    @Before
-    public void setUp() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        mServiceIntent = new Intent(context, TestService.class);
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(TestService.ACTION_LOG_EVENT);
-
-        // Overcautiousness: each EventLogger has its own events list, so one bad test won't spoil
-        // others.
-        mLoggerEvents = new ArrayList<>();
-        mLogger = new EventLogger(mLoggerEvents);
-        localBroadcastManager.registerReceiver(mLogger, intentFilter);
-
-    }
-
-    @After
-    public void tearDown() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
-        localBroadcastManager.unregisterReceiver(mLogger);
-        mLogger = null;
-        mLoggerEvents = null;
-    }
-
-    @Test
-    public void testUnboundedService() throws TimeoutException, InterruptedException {
-        Context context = InstrumentationRegistry.getTargetContext();
-        context.startService(mServiceIntent);
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-        context.stopService(mServiceIntent);
-        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
-    }
-
-    @Test
-    public void testBoundedService() throws TimeoutException, InterruptedException {
-        ServiceConnection connection = bindToService();
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-        InstrumentationRegistry.getTargetContext().unbindService(connection);
-        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
-    }
-
-    @Test
-    public void testStartBindUnbindStop() throws InterruptedException {
-        Context context = InstrumentationRegistry.getTargetContext();
-        context.startService(mServiceIntent);
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        ServiceConnection connection = bindToService();
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // still the same events
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.unbindService(connection);
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // service is still started (stopServices/stopSelf weren't called)
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.stopService(mServiceIntent);
-        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
-    }
-
-    @Test
-    public void testStartBindStopUnbind() throws InterruptedException {
-        Context context = InstrumentationRegistry.getTargetContext();
-        context.startService(mServiceIntent);
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        ServiceConnection connection = bindToService();
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // still the same events
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.stopService(mServiceIntent);
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // service is still bound
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.unbindService(connection);
-        awaitAndAssertEvents(ON_CREATE, ON_START,
-                ON_STOP, ON_DESTROY);
-    }
-
-    @Test
-    public void testBindStartUnbindStop() throws InterruptedException {
-        Context context = InstrumentationRegistry.getTargetContext();
-        ServiceConnection connection = bindToService();
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-
-        context.startService(mServiceIntent);
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // still the same events
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.unbindService(connection);
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // service is still started (stopServices/stopSelf weren't called)
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.stopService(mServiceIntent);
-        awaitAndAssertEvents(ON_CREATE, ON_START,
-                ON_STOP, ON_DESTROY);
-    }
-
-    @Test
-    public void testBindStartStopUnbind() throws InterruptedException {
-        Context context = InstrumentationRegistry.getTargetContext();
-        ServiceConnection connection = bindToService();
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.startService(mServiceIntent);
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // still the same events
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.stopService(mServiceIntent);
-        // Precaution: give a chance to dispatch events
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        // service is still bound
-        awaitAndAssertEvents(ON_CREATE, ON_START);
-
-        context.unbindService(connection);
-        awaitAndAssertEvents(ON_CREATE, ON_START,
-                ON_STOP, ON_DESTROY);
-    }
-
-    // can't use ServiceTestRule because it proxies connection, so we can't use unbindService method
-    private ServiceConnection bindToService() throws InterruptedException {
-        Context context = InstrumentationRegistry.getTargetContext();
-        final CountDownLatch latch = new CountDownLatch(1);
-        ServiceConnection connection = new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName name, IBinder service) {
-                latch.countDown();
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName name) {
-
-            }
-        };
-
-        boolean success = context.bindService(mServiceIntent, connection, Context.BIND_AUTO_CREATE);
-        assertThat(success, is(true));
-        boolean awaited = latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
-        assertThat(awaited, is(true));
-        return connection;
-    }
-
-    private void awaitAndAssertEvents(Event... events) throws InterruptedException {
-        //noinspection SynchronizeOnNonFinalField
-        synchronized (mLoggerEvents) {
-            int retryCount = 0;
-            while (mLoggerEvents.size() < events.length && retryCount++ < RETRY_NUMBER) {
-                mLoggerEvents.wait(TIMEOUT);
-            }
-            assertThat(mLoggerEvents, is(Arrays.asList(events)));
-        }
-    }
-
-    private static class EventLogger extends BroadcastReceiver {
-        private final List<Event> mLoggerEvents;
-
-        private EventLogger(List<Event> loggerEvents) {
-            mLoggerEvents = loggerEvents;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLoggerEvents) {
-                mLoggerEvents.add((Event) intent.getSerializableExtra(TestService.EXTRA_KEY_EVENT));
-                mLoggerEvents.notifyAll();
-            }
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTest.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTest.java
deleted file mode 100644
index 03ebdf3..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.app.Instrumentation;
-import android.arch.lifecycle.viewmodeltest.TestViewModel;
-import android.arch.lifecycle.viewmodeltest.ViewModelActivity;
-import android.arch.lifecycle.viewmodeltest.ViewModelActivity.ViewModelFragment;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-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;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ViewModelTest {
-    private static final int TIMEOUT = 2; // secs
-
-    @Rule
-    public ActivityTestRule<ViewModelActivity> mActivityRule =
-            new ActivityTestRule<>(ViewModelActivity.class);
-
-    @Test
-    public void ensureSameViewHolders() throws Throwable {
-        final TestViewModel[] activityModel = new TestViewModel[1];
-        final TestViewModel[] defaultActivityModel = new TestViewModel[1];
-        final TestViewModel[] fragment1Model = new TestViewModel[1];
-        final TestViewModel[] fragment2Model = new TestViewModel[1];
-        final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1];
-        viewModelActivity[0] = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
-                        ViewModelActivity.FRAGMENT_TAG_1);
-                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
-                        ViewModelActivity.FRAGMENT_TAG_2);
-                assertThat(fragment1, notNullValue());
-                assertThat(fragment2, notNullValue());
-                assertThat(fragment1.activityModel, is(fragment2.activityModel));
-                assertThat(fragment1.fragmentModel, not(is(fragment2.activityModel)));
-                assertThat(mActivityRule.getActivity().activityModel, is(fragment1.activityModel));
-                activityModel[0] = mActivityRule.getActivity().activityModel;
-                defaultActivityModel[0] = mActivityRule.getActivity().defaultActivityModel;
-                assertThat(defaultActivityModel[0], not(is(activityModel[0])));
-                fragment1Model[0] = fragment1.fragmentModel;
-                fragment2Model[0] = fragment2.fragmentModel;
-            }
-        });
-        viewModelActivity[0] = recreateActivity();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
-                        ViewModelActivity.FRAGMENT_TAG_1);
-                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
-                        ViewModelActivity.FRAGMENT_TAG_2);
-                assertThat(fragment1, notNullValue());
-                assertThat(fragment2, notNullValue());
-
-                assertThat(fragment1.activityModel, is(activityModel[0]));
-                assertThat(fragment2.activityModel, is(activityModel[0]));
-                assertThat(fragment1.fragmentModel, is(fragment1Model[0]));
-                assertThat(fragment2.fragmentModel, is(fragment2Model[0]));
-                assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0]));
-                assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0]));
-                assertThat(mActivityRule.getActivity().activityModel, is(activityModel[0]));
-                assertThat(mActivityRule.getActivity().defaultActivityModel,
-                        is(defaultActivityModel[0]));
-            }
-        });
-    }
-
-    @Test
-    @UiThreadTest
-    public void testGetApplication() {
-        TestViewModel activityModel = mActivityRule.getActivity().activityModel;
-        assertThat(activityModel.getApplication(),
-                is(InstrumentationRegistry.getTargetContext().getApplicationContext()));
-    }
-
-    @Test
-    public void testOnClear() throws Throwable {
-        final ViewModelActivity activity = mActivityRule.getActivity();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final LifecycleObserver observer = new LifecycleObserver() {
-            @SuppressWarnings("unused")
-            @OnLifecycleEvent(ON_RESUME)
-            void onResume() {
-                try {
-                    final FragmentManager manager = activity.getSupportFragmentManager();
-                    Fragment fragment = new Fragment();
-                    manager.beginTransaction().add(fragment, "temp").commitNow();
-                    ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class);
-                    assertThat(vm.mCleared, is(false));
-                    manager.beginTransaction().remove(fragment).commitNow();
-                    assertThat(vm.mCleared, is(true));
-                } finally {
-                    latch.countDown();
-                }
-            }
-        };
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                activity.getLifecycle().addObserver(observer);
-            }
-        });
-        assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
-    }
-
-    private ViewModelFragment getFragment(FragmentActivity activity, String tag) {
-        return (ViewModelFragment) activity.getSupportFragmentManager()
-                .findFragmentByTag(tag);
-    }
-
-    private ViewModelActivity recreateActivity() throws Throwable {
-        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
-                ViewModelActivity.class.getCanonicalName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-        final ViewModelActivity previous = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                previous.recreate();
-            }
-        });
-        ViewModelActivity result;
-
-        // this guarantee that we will reinstall monitor between notifications about onDestroy
-        // and onCreate
-        //noinspection SynchronizationOnLocalVariableOrMethodParameter
-        synchronized (monitor) {
-            do {
-                // the documentation says "Block until an Activity is created
-                // that matches this monitor." This statement is true, but there are some other
-                // true statements like: "Block until an Activity is destroyed" or
-                // "Block until an Activity is resumed"...
-
-                // this call will release synchronization monitor's monitor
-                result = (ViewModelActivity) monitor.waitForActivityWithTimeout(4000);
-                if (result == null) {
-                    throw new RuntimeException("Timeout. Failed to recreate an activity");
-                }
-            } while (result == previous);
-        }
-        return result;
-    }
-
-    public static class ViewModel1 extends ViewModel {
-        boolean mCleared = false;
-
-        @Override
-        protected void onCleared() {
-            mCleared = true;
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTestInTransaction.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTestInTransaction.java
deleted file mode 100644
index 3832b3b..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/ViewModelTestInTransaction.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.test.annotation.UiThreadTest;
-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.arch.lifecycle.viewmodeltest.TestViewModel;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ViewModelTestInTransaction {
-
-    @Rule
-    public ActivityTestRule<EmptyActivity> mActivityRule =
-            new ActivityTestRule<>(EmptyActivity.class);
-
-    @Test
-    @UiThreadTest
-    public void testViewModelInTransactionActivity() {
-        EmptyActivity activity = mActivityRule.getActivity();
-        TestFragment fragment = new TestFragment();
-        activity.getSupportFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
-        TestViewModel viewModel = ViewModelProviders.of(activity).get(TestViewModel.class);
-        assertThat(viewModel, is(fragment.mViewModel));
-    }
-
-    @Test
-    @UiThreadTest
-    public void testViewModelInTransactionFragment() {
-        EmptyActivity activity = mActivityRule.getActivity();
-        ParentFragment parent = new ParentFragment();
-        activity.getSupportFragmentManager().beginTransaction().add(parent, "parent").commitNow();
-        assertThat(parent.mExecuted, is(true));
-    }
-
-
-    public static class ParentFragment extends Fragment {
-
-        private boolean mExecuted;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            TestFragment fragment = new TestFragment();
-            getChildFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
-            TestViewModel viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
-            assertThat(viewModel, is(fragment.mViewModel));
-            mExecuted = true;
-        }
-    }
-
-    public static class TestFragment extends Fragment {
-
-        TestViewModel mViewModel;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            Fragment parentFragment = getParentFragment();
-            ViewModelProvider provider = parentFragment != null
-                    ? ViewModelProviders.of(parentFragment) : ViewModelProviders.of(getActivity());
-            mViewModel = provider.get(TestViewModel.class);
-            assertThat(mViewModel, notNullValue());
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/EmptyActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/EmptyActivity.java
deleted file mode 100644
index c32c898..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/EmptyActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.activity;
-
-import android.arch.lifecycle.extensions.test.R;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentActivity;
-
-public class EmptyActivity extends FragmentActivity {
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/FragmentLifecycleActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
deleted file mode 100644
index 2eb1cc2..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.activity;
-
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
-import android.arch.lifecycle.extensions.test.R;
-import android.content.Context;
-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;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class FragmentLifecycleActivity extends AppCompatActivity {
-    public static final String NESTED_TAG = "nested_fragment";
-    public static final String MAIN_TAG = "main_fragment";
-    private static final String EXTRA_NESTED = "nested";
-
-    private final List<Lifecycle.Event> mLoggedEvents = Collections
-            .synchronizedList(new ArrayList<Lifecycle.Event>());
-    private LifecycleOwner mObservedOwner;
-    private final CountDownLatch mDestroyLatch = new CountDownLatch(1);
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
-        MainFragment fragment;
-        fragment = new MainFragment();
-        boolean nested = getIntent().getBooleanExtra(EXTRA_NESTED, false);
-        if (nested) {
-            fragment.mNestedFragment = new NestedFragment();
-        }
-        observe(nested ? fragment.mNestedFragment : fragment);
-        getSupportFragmentManager().beginTransaction()
-                .add(R.id.fragment_container, fragment, MAIN_TAG)
-                .commit();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mDestroyLatch.countDown();
-    }
-
-    public void resetEvents() {
-        mLoggedEvents.clear();
-    }
-
-    public static class MainFragment extends Fragment {
-        @Nullable
-        Fragment mNestedFragment;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (mNestedFragment != null) {
-                getChildFragmentManager().beginTransaction()
-                        .add(mNestedFragment, NESTED_TAG)
-                        .commit();
-            }
-        }
-    }
-
-    public static class NestedFragment extends Fragment {
-    }
-
-    public static Intent intentFor(Context context, boolean nested) {
-        Intent intent = new Intent(context, FragmentLifecycleActivity.class);
-        intent.putExtra(EXTRA_NESTED, nested);
-        return intent;
-    }
-
-    public void observe(LifecycleOwner provider) {
-        mObservedOwner = provider;
-        provider.getLifecycle().addObserver(new LifecycleObserver() {
-            @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-            public void anyEvent(@SuppressWarnings("unused") LifecycleOwner owner,
-                    Lifecycle.Event event) {
-                mLoggedEvents.add(event);
-            }
-        });
-    }
-
-    public List<Lifecycle.Event> getLoggedEvents() {
-        return mLoggedEvents;
-    }
-
-    public LifecycleOwner getObservedOwner() {
-        return mObservedOwner;
-    }
-
-    public boolean awaitForDestruction(long timeout, TimeUnit timeUnit)
-            throws InterruptedException {
-        return mDestroyLatch.await(timeout, timeUnit);
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/service/TestService.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/service/TestService.java
deleted file mode 100644
index fcf06cc..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/service/TestService.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.service;
-
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LifecycleService;
-import android.arch.lifecycle.OnLifecycleEvent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.IBinder;
-import android.support.annotation.Nullable;
-import android.support.v4.content.LocalBroadcastManager;
-
-public class TestService extends LifecycleService {
-
-    public static final String ACTION_LOG_EVENT = "ACTION_LOG_EVENT";
-    public static final String EXTRA_KEY_EVENT = "EXTRA_KEY_EVENT";
-
-    private final IBinder mBinder = new Binder();
-
-    public TestService() {
-        getLifecycle().addObserver(new LifecycleObserver() {
-            @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-            public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
-                Context context = (TestService) owner;
-                Intent intent = new Intent(ACTION_LOG_EVENT);
-                intent.putExtra(EXTRA_KEY_EVENT, event);
-                LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
-            }
-        });
-    }
-
-    @Nullable
-    @Override
-    public IBinder onBind(Intent intent) {
-        super.onBind(intent);
-        return mBinder;
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/TestViewModel.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/TestViewModel.java
deleted file mode 100644
index ec3550c..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/TestViewModel.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.viewmodeltest;
-
-import android.app.Application;
-
-import android.arch.lifecycle.AndroidViewModel;
-
-public class TestViewModel extends AndroidViewModel {
-
-    public TestViewModel(Application application) {
-        super(application);
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java b/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
deleted file mode 100644
index 1f9f100..0000000
--- a/lifecycle/extensions/src/androidTest/java/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.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;
-
-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";
-    public static final String FRAGMENT_TAG_2 = "f2";
-
-    public TestViewModel activityModel;
-    public TestViewModel defaultActivityModel;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_view_model);
-        if (savedInstanceState == null) {
-            getSupportFragmentManager().beginTransaction()
-                    .add(R.id.fragment_container, new ViewModelFragment(), FRAGMENT_TAG_1)
-                    .add(new ViewModelFragment(), FRAGMENT_TAG_2)
-                    .commit();
-        }
-        activityModel = ViewModelProviders.of(this).get(KEY_ACTIVITY_MODEL, TestViewModel.class);
-        defaultActivityModel = ViewModelProviders.of(this).get(TestViewModel.class);
-    }
-
-    public static class ViewModelFragment extends Fragment {
-        public TestViewModel fragmentModel;
-        public TestViewModel activityModel;
-        public TestViewModel defaultActivityModel;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            fragmentModel = ViewModelProviders.of(this).get(KEY_FRAGMENT_MODEL,
-                    TestViewModel.class);
-            activityModel = ViewModelProviders.of(getActivity()).get(KEY_ACTIVITY_MODEL,
-                    TestViewModel.class);
-            defaultActivityModel = ViewModelProviders.of(getActivity()).get(TestViewModel.class);
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/EmptyActivity.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/EmptyActivity.java
new file mode 100644
index 0000000..33fd260
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.fragment.app.FragmentActivity;
+
+public class EmptyActivity extends FragmentActivity {
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/FragmentLifecycleInActivityTest.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/FragmentLifecycleInActivityTest.java
new file mode 100644
index 0000000..40c7225
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/FragmentLifecycleInActivityTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+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.app.Instrumentation;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.activity.FragmentLifecycleActivity;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(Parameterized.class)
+public class FragmentLifecycleInActivityTest {
+
+    private static final long TIMEOUT = 2; //sec
+
+    @Rule
+    public ActivityTestRule<FragmentLifecycleActivity> mActivityRule =
+            new ActivityTestRule<>(FragmentLifecycleActivity.class, false, false);
+
+    private Instrumentation mInstrumentation;
+
+    @SuppressWarnings("WeakerAccess")
+    @Parameterized.Parameter
+    public boolean mNested;
+
+    @Parameterized.Parameters(name = "nested_{0}")
+    public static Object[][] params() {
+        return new Object[][]{new Object[]{false}, new Object[]{true}};
+    }
+
+    @Before
+    public void getInstrumentation() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    private void reset() {
+        mActivityRule.getActivity().resetEvents();
+    }
+
+    @Test
+    public void testFullEvents() throws Throwable {
+        final FragmentLifecycleActivity activity = launchActivity();
+        waitForIdle();
+        assertEvents(ON_CREATE, ON_START, ON_RESUME);
+        reset();
+        finishActivity(activity);
+        assertEvents(ON_PAUSE, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testStopStart() throws Throwable {
+        final FragmentLifecycleActivity activity = launchActivity();
+        waitForIdle();
+        assertEvents(ON_CREATE, ON_START, ON_RESUME);
+        reset();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mInstrumentation.callActivityOnPause(activity);
+                mInstrumentation.callActivityOnStop(activity);
+            }
+        });
+        waitForIdle();
+        assertEvents(ON_PAUSE, ON_STOP);
+        reset();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mInstrumentation.callActivityOnStart(activity);
+                mInstrumentation.callActivityOnResume(activity);
+            }
+        });
+        waitForIdle();
+        assertEvents(ON_START, ON_RESUME);
+    }
+
+    private FragmentLifecycleActivity launchActivity() throws Throwable {
+        Intent intent = FragmentLifecycleActivity.intentFor(mInstrumentation.getTargetContext(),
+                mNested);
+        final FragmentLifecycleActivity activity = mActivityRule.launchActivity(intent);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Fragment main = activity.getSupportFragmentManager()
+                        .findFragmentByTag(FragmentLifecycleActivity.MAIN_TAG);
+                assertThat("test sanity", main, notNullValue());
+                Fragment nestedFragment = main.getChildFragmentManager()
+                        .findFragmentByTag(FragmentLifecycleActivity.NESTED_TAG);
+                assertThat("test sanity", nestedFragment != null, is(mNested));
+            }
+        });
+        assertThat(activity.getObservedOwner(), instanceOf(
+                mNested ? FragmentLifecycleActivity.NestedFragment.class
+                        : FragmentLifecycleActivity.MainFragment.class
+        ));
+        return activity;
+    }
+
+    private void waitForIdle() {
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void finishActivity(final FragmentLifecycleActivity activity)
+            throws InterruptedException {
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.finish();
+            }
+        });
+        assertThat(activity.awaitForDestruction(TIMEOUT, TimeUnit.SECONDS), is(true));
+    }
+
+    private void assertEvents(Lifecycle.Event... events) {
+        assertThat(mActivityRule.getActivity().getLoggedEvents(), is(Arrays.asList(events)));
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/FragmentOperationsLifecycleTest.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/FragmentOperationsLifecycleTest.java
new file mode 100644
index 0000000..0e30381
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/FragmentOperationsLifecycleTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import static java.util.Arrays.asList;
+
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.activity.EmptyActivity;
+import androidx.lifecycle.extensions.test.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentOperationsLifecycleTest {
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityTestRule = new ActivityTestRule<>(
+            EmptyActivity.class);
+
+    @Test
+    @UiThreadTest
+    public void addRemoveFragment() {
+        EmptyActivity activity = mActivityTestRule.getActivity();
+        Fragment fragment = new Fragment();
+        FragmentManager fm = activity.getSupportFragmentManager();
+        fm.beginTransaction().add(fragment, "tag").commitNow();
+        CollectingObserver observer = observeAndCollectIn(fragment);
+        assertThat(observer.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+        fm.beginTransaction().remove(fragment).commitNow();
+        assertThat(observer.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
+        fm.beginTransaction().add(fragment, "tag").commitNow();
+        assertThat(observer.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+    }
+
+    @Test
+    @UiThreadTest
+    public void fragmentInBackstack() {
+        EmptyActivity activity = mActivityTestRule.getActivity();
+        Fragment fragment1 = new Fragment();
+        FragmentManager fm = activity.getSupportFragmentManager();
+        fm.beginTransaction().add(R.id.fragment_container, fragment1, "tag").addToBackStack(null)
+                .commit();
+        fm.executePendingTransactions();
+        CollectingObserver observer1 = observeAndCollectIn(fragment1);
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+
+        Fragment fragment2 = new Fragment();
+        fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
+                .commit();
+        fm.executePendingTransactions();
+
+        CollectingObserver observer2 = observeAndCollectIn(fragment2);
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP)));
+        assertThat(observer2.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
+
+        assertThat(fm.popBackStackImmediate(), is(true));
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_START, ON_RESUME)));
+        assertThat(observer2.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
+
+        assertThat(fm.popBackStackImmediate(), is(true));
+        assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
+    }
+
+    private static CollectingObserver observeAndCollectIn(Fragment fragment) {
+        CollectingObserver observer = new CollectingObserver();
+        fragment.getLifecycle().addObserver(observer);
+        return observer;
+    }
+
+    private static class CollectingObserver implements LifecycleObserver {
+        final List<Lifecycle.Event> mCollectedEvents = new ArrayList<>();
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+        public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+            mCollectedEvents.add(event);
+        }
+
+        List<Lifecycle.Event> getEventsAndReset() {
+            ArrayList<Lifecycle.Event> events = new ArrayList<>(mCollectedEvents);
+            mCollectedEvents.clear();
+            return events;
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.java
new file mode 100644
index 0000000..7f58907
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ServiceLifecycleTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.service.TestService;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ServiceLifecycleTest {
+
+    private static final int RETRY_NUMBER = 5;
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+    private Intent mServiceIntent;
+
+    private volatile List<Event> mLoggerEvents;
+    private EventLogger mLogger;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mServiceIntent = new Intent(context, TestService.class);
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TestService.ACTION_LOG_EVENT);
+
+        // Overcautiousness: each EventLogger has its own events list, so one bad test won't spoil
+        // others.
+        mLoggerEvents = new ArrayList<>();
+        mLogger = new EventLogger(mLoggerEvents);
+        localBroadcastManager.registerReceiver(mLogger, intentFilter);
+
+    }
+
+    @After
+    public void tearDown() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
+        localBroadcastManager.unregisterReceiver(mLogger);
+        mLogger = null;
+        mLoggerEvents = null;
+    }
+
+    @Test
+    public void testUnboundedService() throws TimeoutException, InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.startService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+        context.stopService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testBoundedService() throws TimeoutException, InterruptedException {
+        ServiceConnection connection = bindToService();
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+        InstrumentationRegistry.getTargetContext().unbindService(connection);
+        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testStartBindUnbindStop() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.startService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        ServiceConnection connection = bindToService();
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still started (stopServices/stopSelf weren't called)
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START, ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testStartBindStopUnbind() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.startService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        ServiceConnection connection = bindToService();
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still bound
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        awaitAndAssertEvents(ON_CREATE, ON_START,
+                ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testBindStartUnbindStop() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        ServiceConnection connection = bindToService();
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+
+        context.startService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still started (stopServices/stopSelf weren't called)
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        awaitAndAssertEvents(ON_CREATE, ON_START,
+                ON_STOP, ON_DESTROY);
+    }
+
+    @Test
+    public void testBindStartStopUnbind() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        ServiceConnection connection = bindToService();
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.startService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // still the same events
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.stopService(mServiceIntent);
+        // Precaution: give a chance to dispatch events
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // service is still bound
+        awaitAndAssertEvents(ON_CREATE, ON_START);
+
+        context.unbindService(connection);
+        awaitAndAssertEvents(ON_CREATE, ON_START,
+                ON_STOP, ON_DESTROY);
+    }
+
+    // can't use ServiceTestRule because it proxies connection, so we can't use unbindService method
+    private ServiceConnection bindToService() throws InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        final CountDownLatch latch = new CountDownLatch(1);
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                latch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+
+            }
+        };
+
+        boolean success = context.bindService(mServiceIntent, connection, Context.BIND_AUTO_CREATE);
+        assertThat(success, is(true));
+        boolean awaited = latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+        assertThat(awaited, is(true));
+        return connection;
+    }
+
+    private void awaitAndAssertEvents(Event... events) throws InterruptedException {
+        //noinspection SynchronizeOnNonFinalField
+        synchronized (mLoggerEvents) {
+            int retryCount = 0;
+            while (mLoggerEvents.size() < events.length && retryCount++ < RETRY_NUMBER) {
+                mLoggerEvents.wait(TIMEOUT);
+            }
+            assertThat(mLoggerEvents, is(Arrays.asList(events)));
+        }
+    }
+
+    private static class EventLogger extends BroadcastReceiver {
+        private final List<Event> mLoggerEvents;
+
+        private EventLogger(List<Event> loggerEvents) {
+            mLoggerEvents = loggerEvents;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLoggerEvents) {
+                mLoggerEvents.add((Event) intent.getSerializableExtra(TestService.EXTRA_KEY_EVENT));
+                mLoggerEvents.notifyAll();
+            }
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ViewModelTest.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ViewModelTest.java
new file mode 100644
index 0000000..89d32ff
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ViewModelTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.viewmodeltest.TestViewModel;
+import androidx.lifecycle.viewmodeltest.ViewModelActivity;
+import androidx.lifecycle.viewmodeltest.ViewModelActivity.ViewModelFragment;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewModelTest {
+    private static final int TIMEOUT = 2; // secs
+
+    @Rule
+    public ActivityTestRule<ViewModelActivity> mActivityRule =
+            new ActivityTestRule<>(ViewModelActivity.class);
+
+    @Test
+    public void ensureSameViewHolders() throws Throwable {
+        final TestViewModel[] activityModel = new TestViewModel[1];
+        final TestViewModel[] defaultActivityModel = new TestViewModel[1];
+        final TestViewModel[] fragment1Model = new TestViewModel[1];
+        final TestViewModel[] fragment2Model = new TestViewModel[1];
+        final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1];
+        viewModelActivity[0] = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_1);
+                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_2);
+                assertThat(fragment1, notNullValue());
+                assertThat(fragment2, notNullValue());
+                assertThat(fragment1.activityModel, is(fragment2.activityModel));
+                assertThat(fragment1.fragmentModel, not(is(fragment2.activityModel)));
+                assertThat(mActivityRule.getActivity().activityModel, is(fragment1.activityModel));
+                activityModel[0] = mActivityRule.getActivity().activityModel;
+                defaultActivityModel[0] = mActivityRule.getActivity().defaultActivityModel;
+                assertThat(defaultActivityModel[0], not(is(activityModel[0])));
+                fragment1Model[0] = fragment1.fragmentModel;
+                fragment2Model[0] = fragment2.fragmentModel;
+            }
+        });
+        viewModelActivity[0] = recreateActivity();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_1);
+                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
+                        ViewModelActivity.FRAGMENT_TAG_2);
+                assertThat(fragment1, notNullValue());
+                assertThat(fragment2, notNullValue());
+
+                assertThat(fragment1.activityModel, is(activityModel[0]));
+                assertThat(fragment2.activityModel, is(activityModel[0]));
+                assertThat(fragment1.fragmentModel, is(fragment1Model[0]));
+                assertThat(fragment2.fragmentModel, is(fragment2Model[0]));
+                assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0]));
+                assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0]));
+                assertThat(mActivityRule.getActivity().activityModel, is(activityModel[0]));
+                assertThat(mActivityRule.getActivity().defaultActivityModel,
+                        is(defaultActivityModel[0]));
+            }
+        });
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetApplication() {
+        TestViewModel activityModel = mActivityRule.getActivity().activityModel;
+        assertThat(activityModel.getApplication(),
+                is(InstrumentationRegistry.getTargetContext().getApplicationContext()));
+    }
+
+    @Test
+    public void testOnClear() throws Throwable {
+        final ViewModelActivity activity = mActivityRule.getActivity();
+        final CountDownLatch latch = new CountDownLatch(1);
+        final LifecycleObserver observer = new LifecycleObserver() {
+            @SuppressWarnings("unused")
+            @OnLifecycleEvent(ON_RESUME)
+            void onResume() {
+                try {
+                    final FragmentManager manager = activity.getSupportFragmentManager();
+                    Fragment fragment = new Fragment();
+                    manager.beginTransaction().add(fragment, "temp").commitNow();
+                    ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class);
+                    assertThat(vm.mCleared, is(false));
+                    manager.beginTransaction().remove(fragment).commitNow();
+                    assertThat(vm.mCleared, is(true));
+                } finally {
+                    latch.countDown();
+                }
+            }
+        };
+
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                activity.getLifecycle().addObserver(observer);
+            }
+        });
+        assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
+    }
+
+    private ViewModelFragment getFragment(FragmentActivity activity, String tag) {
+        return (ViewModelFragment) activity.getSupportFragmentManager()
+                .findFragmentByTag(tag);
+    }
+
+    private ViewModelActivity recreateActivity() throws Throwable {
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                ViewModelActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+        final ViewModelActivity previous = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                previous.recreate();
+            }
+        });
+        ViewModelActivity result;
+
+        // this guarantee that we will reinstall monitor between notifications about onDestroy
+        // and onCreate
+        //noinspection SynchronizationOnLocalVariableOrMethodParameter
+        synchronized (monitor) {
+            do {
+                // the documentation says "Block until an Activity is created
+                // that matches this monitor." This statement is true, but there are some other
+                // true statements like: "Block until an Activity is destroyed" or
+                // "Block until an Activity is resumed"...
+
+                // this call will release synchronization monitor's monitor
+                result = (ViewModelActivity) monitor.waitForActivityWithTimeout(4000);
+                if (result == null) {
+                    throw new RuntimeException("Timeout. Failed to recreate an activity");
+                }
+            } while (result == previous);
+        }
+        return result;
+    }
+
+    public static class ViewModel1 extends ViewModel {
+        boolean mCleared = false;
+
+        @Override
+        protected void onCleared() {
+            mCleared = true;
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ViewModelTestInTransaction.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ViewModelTestInTransaction.java
new file mode 100644
index 0000000..091bede
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/ViewModelTestInTransaction.java
@@ -0,0 +1,94 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.os.Bundle;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.viewmodeltest.TestViewModel;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewModelTestInTransaction {
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityRule =
+            new ActivityTestRule<>(EmptyActivity.class);
+
+    @Test
+    @UiThreadTest
+    public void testViewModelInTransactionActivity() {
+        EmptyActivity activity = mActivityRule.getActivity();
+        TestFragment fragment = new TestFragment();
+        activity.getSupportFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
+        TestViewModel viewModel = ViewModelProviders.of(activity).get(TestViewModel.class);
+        assertThat(viewModel, is(fragment.mViewModel));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testViewModelInTransactionFragment() {
+        EmptyActivity activity = mActivityRule.getActivity();
+        ParentFragment parent = new ParentFragment();
+        activity.getSupportFragmentManager().beginTransaction().add(parent, "parent").commitNow();
+        assertThat(parent.mExecuted, is(true));
+    }
+
+
+    public static class ParentFragment extends Fragment {
+
+        private boolean mExecuted;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            TestFragment fragment = new TestFragment();
+            getChildFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
+            TestViewModel viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
+            assertThat(viewModel, is(fragment.mViewModel));
+            mExecuted = true;
+        }
+    }
+
+    public static class TestFragment extends Fragment {
+
+        TestViewModel mViewModel;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Fragment parentFragment = getParentFragment();
+            ViewModelProvider provider = parentFragment != null
+                    ? ViewModelProviders.of(parentFragment) : ViewModelProviders.of(getActivity());
+            mViewModel = provider.get(TestViewModel.class);
+            assertThat(mViewModel, notNullValue());
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/activity/EmptyActivity.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/activity/EmptyActivity.java
new file mode 100644
index 0000000..66bf9eb
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/activity/EmptyActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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 androidx.lifecycle.activity;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.extensions.test.R;
+
+public class EmptyActivity extends FragmentActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/activity/FragmentLifecycleActivity.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/activity/FragmentLifecycleActivity.java
new file mode 100644
index 0000000..597111d
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/activity/FragmentLifecycleActivity.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.extensions.test.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class FragmentLifecycleActivity extends AppCompatActivity {
+    public static final String NESTED_TAG = "nested_fragment";
+    public static final String MAIN_TAG = "main_fragment";
+    private static final String EXTRA_NESTED = "nested";
+
+    private final List<Lifecycle.Event> mLoggedEvents = Collections
+            .synchronizedList(new ArrayList<Lifecycle.Event>());
+    private LifecycleOwner mObservedOwner;
+    private final CountDownLatch mDestroyLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        MainFragment fragment;
+        fragment = new MainFragment();
+        boolean nested = getIntent().getBooleanExtra(EXTRA_NESTED, false);
+        if (nested) {
+            fragment.mNestedFragment = new NestedFragment();
+        }
+        observe(nested ? fragment.mNestedFragment : fragment);
+        getSupportFragmentManager().beginTransaction()
+                .add(R.id.fragment_container, fragment, MAIN_TAG)
+                .commit();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDestroyLatch.countDown();
+    }
+
+    public void resetEvents() {
+        mLoggedEvents.clear();
+    }
+
+    public static class MainFragment extends Fragment {
+        @Nullable
+        Fragment mNestedFragment;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (mNestedFragment != null) {
+                getChildFragmentManager().beginTransaction()
+                        .add(mNestedFragment, NESTED_TAG)
+                        .commit();
+            }
+        }
+    }
+
+    public static class NestedFragment extends Fragment {
+    }
+
+    public static Intent intentFor(Context context, boolean nested) {
+        Intent intent = new Intent(context, FragmentLifecycleActivity.class);
+        intent.putExtra(EXTRA_NESTED, nested);
+        return intent;
+    }
+
+    public void observe(LifecycleOwner provider) {
+        mObservedOwner = provider;
+        provider.getLifecycle().addObserver(new LifecycleObserver() {
+            @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+            public void anyEvent(@SuppressWarnings("unused") LifecycleOwner owner,
+                    Lifecycle.Event event) {
+                mLoggedEvents.add(event);
+            }
+        });
+    }
+
+    public List<Lifecycle.Event> getLoggedEvents() {
+        return mLoggedEvents;
+    }
+
+    public LifecycleOwner getObservedOwner() {
+        return mObservedOwner;
+    }
+
+    public boolean awaitForDestruction(long timeout, TimeUnit timeUnit)
+            throws InterruptedException {
+        return mDestroyLatch.await(timeout, timeUnit);
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/service/TestService.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/service/TestService.java
new file mode 100644
index 0000000..afc9dc8
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/service/TestService.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.service;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleService;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+public class TestService extends LifecycleService {
+
+    public static final String ACTION_LOG_EVENT = "ACTION_LOG_EVENT";
+    public static final String EXTRA_KEY_EVENT = "EXTRA_KEY_EVENT";
+
+    private final IBinder mBinder = new Binder();
+
+    public TestService() {
+        getLifecycle().addObserver(new LifecycleObserver() {
+            @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+            public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+                Context context = (TestService) owner;
+                Intent intent = new Intent(ACTION_LOG_EVENT);
+                intent.putExtra(EXTRA_KEY_EVENT, event);
+                LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+            }
+        });
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        super.onBind(intent);
+        return mBinder;
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/viewmodeltest/TestViewModel.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/viewmodeltest/TestViewModel.java
new file mode 100644
index 0000000..40c27b9
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/viewmodeltest/TestViewModel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodeltest;
+
+import android.app.Application;
+
+import androidx.lifecycle.AndroidViewModel;
+
+public class TestViewModel extends AndroidViewModel {
+
+    public TestViewModel(Application application) {
+        super(application);
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/viewmodeltest/ViewModelActivity.java b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/viewmodeltest/ViewModelActivity.java
new file mode 100644
index 0000000..c20e819
--- /dev/null
+++ b/lifecycle/extensions/src/androidTest/java/androidx/lifecycle/viewmodeltest/ViewModelActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodeltest;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.lifecycle.extensions.test.R;
+
+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";
+    public static final String FRAGMENT_TAG_2 = "f2";
+
+    public TestViewModel activityModel;
+    public TestViewModel defaultActivityModel;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_view_model);
+        if (savedInstanceState == null) {
+            getSupportFragmentManager().beginTransaction()
+                    .add(R.id.fragment_container, new ViewModelFragment(), FRAGMENT_TAG_1)
+                    .add(new ViewModelFragment(), FRAGMENT_TAG_2)
+                    .commit();
+        }
+        activityModel = ViewModelProviders.of(this).get(KEY_ACTIVITY_MODEL, TestViewModel.class);
+        defaultActivityModel = ViewModelProviders.of(this).get(TestViewModel.class);
+    }
+
+    public static class ViewModelFragment extends Fragment {
+        public TestViewModel fragmentModel;
+        public TestViewModel activityModel;
+        public TestViewModel defaultActivityModel;
+
+        @Override
+        public void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            fragmentModel = ViewModelProviders.of(this).get(KEY_FRAGMENT_MODEL,
+                    TestViewModel.class);
+            activityModel = ViewModelProviders.of(getActivity()).get(KEY_ACTIVITY_MODEL,
+                    TestViewModel.class);
+            defaultActivityModel = ViewModelProviders.of(getActivity()).get(TestViewModel.class);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/androidTest/res/layout/activity_main.xml b/lifecycle/extensions/src/androidTest/res/layout/activity_main.xml
index 305bccc..86730b2 100644
--- a/lifecycle/extensions/src/androidTest/res/layout/activity_main.xml
+++ b/lifecycle/extensions/src/androidTest/res/layout/activity_main.xml
@@ -21,7 +21,7 @@
     android:id="@+id/activity_main"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context="android.arch.lifecycle.activity.FragmentLifecycleActivity">
+    tools:context="androidx.lifecycle.activity.FragmentLifecycleActivity">
     <FrameLayout android:id="@+id/fragment_container"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"></FrameLayout>
diff --git a/lifecycle/extensions/src/main/AndroidManifest.xml b/lifecycle/extensions/src/main/AndroidManifest.xml
index f8a5c48..4706adf 100644
--- a/lifecycle/extensions/src/main/AndroidManifest.xml
+++ b/lifecycle/extensions/src/main/AndroidManifest.xml
@@ -15,11 +15,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle.extensions">
+          package="androidx.lifecycle.extensions">
     <application>
         <provider android:authorities="${applicationId}.lifecycle-trojan"
             android:multiprocess="true"
             android:exported="false"
-            android:name="android.arch.lifecycle.ProcessLifecycleOwnerInitializer"/>
+            android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"/>
     </application>
 </manifest>
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/EmptyActivityLifecycleCallbacks.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/EmptyActivityLifecycleCallbacks.java
deleted file mode 100644
index ed41f2d..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/EmptyActivityLifecycleCallbacks.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Activity;
-import android.app.Application;
-import android.os.Bundle;
-
-class EmptyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
-    @Override
-    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-    }
-
-    @Override
-    public void onActivityStarted(Activity activity) {
-    }
-
-    @Override
-    public void onActivityResumed(Activity activity) {
-    }
-
-    @Override
-    public void onActivityPaused(Activity activity) {
-    }
-
-    @Override
-    public void onActivityStopped(Activity activity) {
-    }
-
-    @Override
-    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
-    }
-
-    @Override
-    public void onActivityDestroyed(Activity activity) {
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/HolderFragment.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/HolderFragment.java
deleted file mode 100644
index ca5e181..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/HolderFragment.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Activity;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class HolderFragment extends Fragment implements ViewModelStoreOwner {
-    private static final String LOG_TAG = "ViewModelStores";
-
-    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
-
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static final String HOLDER_TAG =
-            "android.arch.lifecycle.state.StateProviderHolderFragment";
-
-    private ViewModelStore mViewModelStore = new ViewModelStore();
-
-    public HolderFragment() {
-        setRetainInstance(true);
-    }
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        sHolderFragmentManager.holderFragmentCreated(this);
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mViewModelStore.clear();
-    }
-
-    @NonNull
-    @Override
-    public ViewModelStore getViewModelStore() {
-        return mViewModelStore;
-    }
-
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
-        return sHolderFragmentManager.holderFragmentFor(activity);
-    }
-
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static HolderFragment holderFragmentFor(Fragment fragment) {
-        return sHolderFragmentManager.holderFragmentFor(fragment);
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class HolderFragmentManager {
-        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
-        private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();
-
-        private ActivityLifecycleCallbacks mActivityCallbacks =
-                new EmptyActivityLifecycleCallbacks() {
-                    @Override
-                    public void onActivityDestroyed(Activity activity) {
-                        HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
-                        if (fragment != null) {
-                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + activity);
-                        }
-                    }
-                };
-
-        private boolean mActivityCallbacksIsAdded = false;
-
-        private FragmentLifecycleCallbacks mParentDestroyedCallback =
-                new FragmentLifecycleCallbacks() {
-                    @Override
-                    public void onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) {
-                        super.onFragmentDestroyed(fm, parentFragment);
-                        HolderFragment fragment = mNotCommittedFragmentHolders.remove(
-                                parentFragment);
-                        if (fragment != null) {
-                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + parentFragment);
-                        }
-                    }
-                };
-
-        void holderFragmentCreated(Fragment holderFragment) {
-            Fragment parentFragment = holderFragment.getParentFragment();
-            if (parentFragment != null) {
-                mNotCommittedFragmentHolders.remove(parentFragment);
-                parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
-                        mParentDestroyedCallback);
-            } else {
-                mNotCommittedActivityHolders.remove(holderFragment.getActivity());
-            }
-        }
-
-        private static HolderFragment findHolderFragment(FragmentManager manager) {
-            if (manager.isDestroyed()) {
-                throw new IllegalStateException("Can't access ViewModels from onDestroy");
-            }
-
-            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
-            if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
-                throw new IllegalStateException("Unexpected "
-                        + "fragment instance was returned by HOLDER_TAG");
-            }
-            return (HolderFragment) fragmentByTag;
-        }
-
-        private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
-            HolderFragment holder = new HolderFragment();
-            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
-            return holder;
-        }
-
-        HolderFragment holderFragmentFor(FragmentActivity activity) {
-            FragmentManager fm = activity.getSupportFragmentManager();
-            HolderFragment holder = findHolderFragment(fm);
-            if (holder != null) {
-                return holder;
-            }
-            holder = mNotCommittedActivityHolders.get(activity);
-            if (holder != null) {
-                return holder;
-            }
-
-            if (!mActivityCallbacksIsAdded) {
-                mActivityCallbacksIsAdded = true;
-                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
-            }
-            holder = createHolderFragment(fm);
-            mNotCommittedActivityHolders.put(activity, holder);
-            return holder;
-        }
-
-        HolderFragment holderFragmentFor(Fragment parentFragment) {
-            FragmentManager fm = parentFragment.getChildFragmentManager();
-            HolderFragment holder = findHolderFragment(fm);
-            if (holder != null) {
-                return holder;
-            }
-            holder = mNotCommittedFragmentHolders.get(parentFragment);
-            if (holder != null) {
-                return holder;
-            }
-
-            parentFragment.getFragmentManager()
-                    .registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false);
-            holder = createHolderFragment(fm);
-            mNotCommittedFragmentHolders.put(parentFragment, holder);
-            return holder;
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleDispatcher.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleDispatcher.java
deleted file mode 100644
index 9fdec95..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleDispatcher.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.Lifecycle.State.CREATED;
-
-import android.app.Activity;
-import android.app.Application;
-import android.arch.lifecycle.Lifecycle.State;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-
-import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * When initialized, it hooks into the Activity callback of the Application and observes
- * Activities. It is responsible to hook in child-fragments to activities and fragments to report
- * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle
- * providers related to an activity as soon it is not safe to run a fragment transaction in this
- * activity.
- */
-class LifecycleDispatcher {
-
-    private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
-            + ".LifecycleDispatcher.report_fragment_tag";
-
-    private static AtomicBoolean sInitialized = new AtomicBoolean(false);
-
-    static void init(Context context) {
-        if (sInitialized.getAndSet(true)) {
-            return;
-        }
-        ((Application) context.getApplicationContext())
-                .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
-        private final FragmentCallback mFragmentCallback;
-
-        DispatcherActivityCallback() {
-            mFragmentCallback = new FragmentCallback();
-        }
-
-        @Override
-        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-            if (activity instanceof FragmentActivity) {
-                ((FragmentActivity) activity).getSupportFragmentManager()
-                        .registerFragmentLifecycleCallbacks(mFragmentCallback, true);
-            }
-            ReportFragment.injectIfNeededIn(activity);
-        }
-
-        @Override
-        public void onActivityStopped(Activity activity) {
-            if (activity instanceof FragmentActivity) {
-                markState((FragmentActivity) activity, CREATED);
-            }
-        }
-
-        @Override
-        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
-            if (activity instanceof FragmentActivity) {
-                markState((FragmentActivity) activity, CREATED);
-            }
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    public static class DestructionReportFragment extends Fragment {
-        @Override
-        public void onPause() {
-            super.onPause();
-            dispatch(ON_PAUSE);
-        }
-
-        @Override
-        public void onStop() {
-            super.onStop();
-            dispatch(ON_STOP);
-        }
-
-        @Override
-        public void onDestroy() {
-            super.onDestroy();
-            dispatch(ON_DESTROY);
-        }
-
-        protected void dispatch(Lifecycle.Event event) {
-            dispatchIfLifecycleOwner(getParentFragment(), event);
-        }
-    }
-
-    private static void markState(FragmentManager manager, State state) {
-        Collection<Fragment> fragments = manager.getFragments();
-        if (fragments == null) {
-            return;
-        }
-        for (Fragment fragment : fragments) {
-            if (fragment == null) {
-                continue;
-            }
-            markStateIn(fragment, state);
-            if (fragment.isAdded()) {
-                markState(fragment.getChildFragmentManager(), state);
-            }
-        }
-    }
-
-    private static void markStateIn(Object object, State state) {
-        if (object instanceof LifecycleRegistryOwner) {
-            LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle();
-            registry.markState(state);
-        }
-    }
-
-    private static void markState(FragmentActivity activity, State state) {
-        markStateIn(activity, state);
-        markState(activity.getSupportFragmentManager(), state);
-    }
-
-    private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) {
-        if (fragment instanceof LifecycleRegistryOwner) {
-            ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks {
-
-        @Override
-        public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
-            dispatchIfLifecycleOwner(f, ON_CREATE);
-
-            if (!(f instanceof LifecycleRegistryOwner)) {
-                return;
-            }
-
-            if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
-                f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(),
-                        REPORT_FRAGMENT_TAG).commit();
-            }
-        }
-
-        @Override
-        public void onFragmentStarted(FragmentManager fm, Fragment f) {
-            dispatchIfLifecycleOwner(f, ON_START);
-        }
-
-        @Override
-        public void onFragmentResumed(FragmentManager fm, Fragment f) {
-            dispatchIfLifecycleOwner(f, ON_RESUME);
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleService.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleService.java
deleted file mode 100644
index adc4ffc..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/LifecycleService.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.support.annotation.CallSuper;
-import android.support.annotation.Nullable;
-
-/**
- * A Service that is also a {@link LifecycleOwner}.
- */
-public class LifecycleService extends Service implements LifecycleOwner {
-
-    private final ServiceLifecycleDispatcher mDispatcher = new ServiceLifecycleDispatcher(this);
-
-    @CallSuper
-    @Override
-    public void onCreate() {
-        mDispatcher.onServicePreSuperOnCreate();
-        super.onCreate();
-    }
-
-    @CallSuper
-    @Nullable
-    @Override
-    public IBinder onBind(Intent intent) {
-        mDispatcher.onServicePreSuperOnBind();
-        return null;
-    }
-
-    @SuppressWarnings("deprecation")
-    @CallSuper
-    @Override
-    public void onStart(Intent intent, int startId) {
-        mDispatcher.onServicePreSuperOnStart();
-        super.onStart(intent, startId);
-    }
-
-    // this method is added only to annotate it with @CallSuper.
-    // In usual service super.onStartCommand is no-op, but in LifecycleService
-    // it results in mDispatcher.onServicePreSuperOnStart() call, because
-    // super.onStartCommand calls onStart().
-    @CallSuper
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        return super.onStartCommand(intent, flags, startId);
-    }
-
-    @CallSuper
-    @Override
-    public void onDestroy() {
-        mDispatcher.onServicePreSuperOnDestroy();
-        super.onDestroy();
-    }
-
-    @Override
-    public Lifecycle getLifecycle() {
-        return mDispatcher.getLifecycle();
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwner.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwner.java
deleted file mode 100644
index 74ea97f..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwner.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Activity;
-import android.app.Application;
-import android.arch.lifecycle.ReportFragment.ActivityInitializationListener;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-
-/**
- * Class that provides lifecycle for the whole application process.
- * <p>
- * You can consider this LifecycleOwner as the composite of all of your Activities, except that
- * {@link Lifecycle.Event#ON_CREATE} will be dispatched once and {@link Lifecycle.Event#ON_DESTROY}
- * will never be dispatched. Other lifecycle events will be dispatched with following rules:
- * ProcessLifecycleOwner will dispatch {@link Lifecycle.Event#ON_START},
- * {@link Lifecycle.Event#ON_RESUME} events, as a first activity moves through these events.
- * {@link Lifecycle.Event#ON_PAUSE}, {@link Lifecycle.Event#ON_STOP}, events will be dispatched with
- * a <b>delay</b> after a last activity
- * passed through them. This delay is long enough to guarantee that ProcessLifecycleOwner
- * won't send any events if activities are destroyed and recreated due to a
- * configuration change.
- *
- * <p>
- * It is useful for use cases where you would like to react on your app coming to the foreground or
- * going to the background and you don't need a milliseconds accuracy in receiving lifecycle
- * events.
- */
-@SuppressWarnings("WeakerAccess")
-public class ProcessLifecycleOwner implements LifecycleOwner {
-
-    @VisibleForTesting
-    static final long TIMEOUT_MS = 700; //mls
-
-    // ground truth counters
-    private int mStartedCounter = 0;
-    private int mResumedCounter = 0;
-
-    private boolean mPauseSent = true;
-    private boolean mStopSent = true;
-
-    private Handler mHandler;
-    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
-
-    private Runnable mDelayedPauseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            dispatchPauseIfNeeded();
-            dispatchStopIfNeeded();
-        }
-    };
-
-    private ActivityInitializationListener mInitializationListener =
-            new ActivityInitializationListener() {
-                @Override
-                public void onCreate() {
-                }
-
-                @Override
-                public void onStart() {
-                    activityStarted();
-                }
-
-                @Override
-                public void onResume() {
-                    activityResumed();
-                }
-            };
-
-    private static final ProcessLifecycleOwner sInstance = new ProcessLifecycleOwner();
-
-    /**
-     * The LifecycleOwner for the whole application process. Note that if your application
-     * has multiple processes, this provider does not know about other processes.
-     *
-     * @return {@link LifecycleOwner} for the whole application.
-     */
-    public static LifecycleOwner get() {
-        return sInstance;
-    }
-
-    static void init(Context context) {
-        sInstance.attach(context);
-    }
-
-    void activityStarted() {
-        mStartedCounter++;
-        if (mStartedCounter == 1 && mStopSent) {
-            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-            mStopSent = false;
-        }
-    }
-
-    void activityResumed() {
-        mResumedCounter++;
-        if (mResumedCounter == 1) {
-            if (mPauseSent) {
-                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
-                mPauseSent = false;
-            } else {
-                mHandler.removeCallbacks(mDelayedPauseRunnable);
-            }
-        }
-    }
-
-    void activityPaused() {
-        mResumedCounter--;
-        if (mResumedCounter == 0) {
-            mHandler.postDelayed(mDelayedPauseRunnable, TIMEOUT_MS);
-        }
-    }
-
-    void activityStopped() {
-        mStartedCounter--;
-        dispatchStopIfNeeded();
-    }
-
-    private void dispatchPauseIfNeeded() {
-        if (mResumedCounter == 0) {
-            mPauseSent = true;
-            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
-        }
-    }
-
-    private void dispatchStopIfNeeded() {
-        if (mStartedCounter == 0 && mPauseSent) {
-            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-            mStopSent = true;
-        }
-    }
-
-    private ProcessLifecycleOwner() {
-    }
-
-    void attach(Context context) {
-        mHandler = new Handler();
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
-        Application app = (Application) context.getApplicationContext();
-        app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
-            @Override
-            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-                ReportFragment.get(activity).setProcessListener(mInitializationListener);
-            }
-
-            @Override
-            public void onActivityPaused(Activity activity) {
-                activityPaused();
-            }
-
-            @Override
-            public void onActivityStopped(Activity activity) {
-                activityStopped();
-            }
-        });
-    }
-
-    @NonNull
-    @Override
-    public Lifecycle getLifecycle() {
-        return mRegistry;
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
deleted file mode 100644
index 6cf80d2..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-/**
- * Internal class to initialize Lifecycles.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ProcessLifecycleOwnerInitializer extends ContentProvider {
-    @Override
-    public boolean onCreate() {
-        LifecycleDispatcher.init(getContext());
-        ProcessLifecycleOwner.init(getContext());
-        return true;
-    }
-
-    @Nullable
-    @Override
-    public Cursor query(@NonNull Uri uri, String[] strings, String s, String[] strings1,
-            String s1) {
-        return null;
-    }
-
-    @Nullable
-    @Override
-    public String getType(@NonNull Uri uri) {
-        return null;
-    }
-
-    @Nullable
-    @Override
-    public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
-        return null;
-    }
-
-    @Override
-    public int delete(@NonNull Uri uri, String s, String[] strings) {
-        return 0;
-    }
-
-    @Override
-    public int update(@NonNull Uri uri, ContentValues contentValues, String s, String[] strings) {
-        return 0;
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ServiceLifecycleDispatcher.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ServiceLifecycleDispatcher.java
deleted file mode 100644
index 6067d7b..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ServiceLifecycleDispatcher.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-import android.support.annotation.NonNull;
-
-/**
- * Helper class to dispatch lifecycle events for a service. Use it only if it is impossible
- * to use {@link LifecycleService}.
- */
-@SuppressWarnings("WeakerAccess")
-public class ServiceLifecycleDispatcher {
-    private final LifecycleRegistry mRegistry;
-    private final Handler mHandler;
-    private DispatchRunnable mLastDispatchRunnable;
-
-    /**
-     * @param provider {@link LifecycleOwner} for a service, usually it is a service itself
-     */
-    public ServiceLifecycleDispatcher(@NonNull LifecycleOwner provider) {
-        mRegistry = new LifecycleRegistry(provider);
-        mHandler = new Handler();
-    }
-
-    private void postDispatchRunnable(Lifecycle.Event event) {
-        if (mLastDispatchRunnable != null) {
-            mLastDispatchRunnable.run();
-        }
-        mLastDispatchRunnable = new DispatchRunnable(mRegistry, event);
-        mHandler.postAtFrontOfQueue(mLastDispatchRunnable);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onCreate()} method, even before super.onCreate call.
-     */
-    public void onServicePreSuperOnCreate() {
-        postDispatchRunnable(Lifecycle.Event.ON_CREATE);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onBind(Intent)} method, even before super.onBind
-     * call.
-     */
-    public void onServicePreSuperOnBind() {
-        postDispatchRunnable(Lifecycle.Event.ON_START);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onStart(Intent, int)} or
-     * {@link Service#onStartCommand(Intent, int, int)} methods, even before
-     * a corresponding super call.
-     */
-    public void onServicePreSuperOnStart() {
-        postDispatchRunnable(Lifecycle.Event.ON_START);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onDestroy()} method, even before super.OnDestroy
-     * call.
-     */
-    public void onServicePreSuperOnDestroy() {
-        postDispatchRunnable(Lifecycle.Event.ON_STOP);
-        postDispatchRunnable(Lifecycle.Event.ON_DESTROY);
-    }
-
-    /**
-     * @return {@link Lifecycle} for the given {@link LifecycleOwner}
-     */
-    public Lifecycle getLifecycle() {
-        return mRegistry;
-    }
-
-    static class DispatchRunnable implements Runnable {
-        private final LifecycleRegistry mRegistry;
-        final Lifecycle.Event mEvent;
-        private boolean mWasExecuted = false;
-
-        DispatchRunnable(@NonNull LifecycleRegistry registry, Lifecycle.Event event) {
-            mRegistry = registry;
-            mEvent = event;
-        }
-
-        @Override
-        public void run() {
-            if (!mWasExecuted) {
-                mRegistry.handleLifecycleEvent(mEvent);
-                mWasExecuted = true;
-            }
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java
deleted file mode 100644
index 0940235..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelProviders.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Activity;
-import android.app.Application;
-import android.arch.lifecycle.ViewModelProvider.Factory;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Utilities methods for {@link ViewModelStore} class.
- */
-public class ViewModelProviders {
-
-    /**
-     * @deprecated This class should not be directly instantiated
-     */
-    @Deprecated
-    public ViewModelProviders() {
-    }
-
-    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}.
-     * <p>
-     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
-     *
-     * @param fragment a fragment, in whose scope ViewModels should be retained
-     * @return a ViewModelProvider instance
-     */
-    @NonNull
-    @MainThread
-    public static ViewModelProvider of(@NonNull Fragment fragment) {
-        return of(fragment, null);
-    }
-
-    /**
-     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
-     * is alive. More detailed explanation is in {@link ViewModel}.
-     * <p>
-     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
-     *
-     * @param activity an activity, in whose scope ViewModels should be retained
-     * @return a ViewModelProvider instance
-     */
-    @NonNull
-    @MainThread
-    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
-        return of(activity, null);
-    }
-
-    /**
-     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
-     * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
-     * <p>
-     * It uses the given {@link Factory} to instantiate new ViewModels.
-     *
-     * @param fragment a fragment, in whose scope ViewModels should be retained
-     * @param factory  a {@code Factory} to instantiate new ViewModels
-     * @return a ViewModelProvider instance
-     */
-    @NonNull
-    @MainThread
-    public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
-        Application application = checkApplication(checkActivity(fragment));
-        if (factory == null) {
-            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
-        }
-        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
-    }
-
-    /**
-     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
-     * is alive. More detailed explanation is in {@link ViewModel}.
-     * <p>
-     * It uses the given {@link Factory} to instantiate new ViewModels.
-     *
-     * @param activity an activity, in whose scope ViewModels should be retained
-     * @param factory  a {@code Factory} to instantiate new ViewModels
-     * @return a ViewModelProvider instance
-     */
-    @NonNull
-    @MainThread
-    public static ViewModelProvider of(@NonNull FragmentActivity activity,
-            @Nullable Factory factory) {
-        Application application = checkApplication(activity);
-        if (factory == null) {
-            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
-        }
-        return new ViewModelProvider(ViewModelStores.of(activity), factory);
-    }
-
-    /**
-     * {@link Factory} which may create {@link AndroidViewModel} and
-     * {@link ViewModel}, which have an empty constructor.
-     *
-     * @deprecated Use {@link ViewModelProvider.AndroidViewModelFactory}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @Deprecated
-    public static class DefaultFactory extends ViewModelProvider.AndroidViewModelFactory {
-        /**
-         * Creates a {@code AndroidViewModelFactory}
-         *
-         * @param application an application to pass in {@link AndroidViewModel}
-         * @deprecated Use {@link ViewModelProvider.AndroidViewModelFactory} or
-         * {@link ViewModelProvider.AndroidViewModelFactory#getInstance(Application)}.
-         */
-        @Deprecated
-        public DefaultFactory(@NonNull Application application) {
-            super(application);
-        }
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java b/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java
deleted file mode 100644
index 348a06e..0000000
--- a/lifecycle/extensions/src/main/java/android/arch/lifecycle/ViewModelStores.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.HolderFragment.holderFragmentFor;
-
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Factory methods for {@link ViewModelStore} class.
- */
-@SuppressWarnings("WeakerAccess")
-public class ViewModelStores {
-
-    private ViewModelStores() {
-    }
-
-    /**
-     * Returns the {@link ViewModelStore} of the given activity.
-     *
-     * @param activity an activity whose {@code ViewModelStore} is requested
-     * @return a {@code ViewModelStore}
-     */
-    @NonNull
-    @MainThread
-    public static ViewModelStore of(@NonNull FragmentActivity activity) {
-        if (activity instanceof ViewModelStoreOwner) {
-            return ((ViewModelStoreOwner) activity).getViewModelStore();
-        }
-        return holderFragmentFor(activity).getViewModelStore();
-    }
-
-    /**
-     * Returns the {@link ViewModelStore} of the given fragment.
-     *
-     * @param fragment a fragment whose {@code ViewModelStore} is requested
-     * @return a {@code ViewModelStore}
-     */
-    @NonNull
-    @MainThread
-    public static ViewModelStore of(@NonNull Fragment fragment) {
-        if (fragment instanceof ViewModelStoreOwner) {
-            return ((ViewModelStoreOwner) fragment).getViewModelStore();
-        }
-        return holderFragmentFor(fragment).getViewModelStore();
-    }
-}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/EmptyActivityLifecycleCallbacks.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/EmptyActivityLifecycleCallbacks.java
new file mode 100644
index 0000000..70518a6
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/EmptyActivityLifecycleCallbacks.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 androidx.lifecycle;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+class EmptyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
+    @Override
+    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+    }
+
+    @Override
+    public void onActivityStarted(Activity activity) {
+    }
+
+    @Override
+    public void onActivityResumed(Activity activity) {
+    }
+
+    @Override
+    public void onActivityPaused(Activity activity) {
+    }
+
+    @Override
+    public void onActivityStopped(Activity activity) {
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+    }
+
+    @Override
+    public void onActivityDestroyed(Activity activity) {
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/HolderFragment.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/HolderFragment.java
new file mode 100644
index 0000000..24fa504
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/HolderFragment.java
@@ -0,0 +1,195 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.app.Activity;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class HolderFragment extends Fragment implements ViewModelStoreOwner {
+    private static final String LOG_TAG = "ViewModelStores";
+
+    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String HOLDER_TAG =
+            "androidx.lifecycle.state.StateProviderHolderFragment";
+
+    private ViewModelStore mViewModelStore = new ViewModelStore();
+
+    public HolderFragment() {
+        setRetainInstance(true);
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sHolderFragmentManager.holderFragmentCreated(this);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mViewModelStore.clear();
+    }
+
+    @NonNull
+    @Override
+    public ViewModelStore getViewModelStore() {
+        return mViewModelStore;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
+        return sHolderFragmentManager.holderFragmentFor(activity);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static HolderFragment holderFragmentFor(Fragment fragment) {
+        return sHolderFragmentManager.holderFragmentFor(fragment);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class HolderFragmentManager {
+        private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
+        private Map<Fragment, HolderFragment> mNotCommittedFragmentHolders = new HashMap<>();
+
+        private ActivityLifecycleCallbacks mActivityCallbacks =
+                new EmptyActivityLifecycleCallbacks() {
+                    @Override
+                    public void onActivityDestroyed(Activity activity) {
+                        HolderFragment fragment = mNotCommittedActivityHolders.remove(activity);
+                        if (fragment != null) {
+                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + activity);
+                        }
+                    }
+                };
+
+        private boolean mActivityCallbacksIsAdded = false;
+
+        private FragmentLifecycleCallbacks mParentDestroyedCallback =
+                new FragmentLifecycleCallbacks() {
+                    @Override
+                    public void onFragmentDestroyed(FragmentManager fm, Fragment parentFragment) {
+                        super.onFragmentDestroyed(fm, parentFragment);
+                        HolderFragment fragment = mNotCommittedFragmentHolders.remove(
+                                parentFragment);
+                        if (fragment != null) {
+                            Log.e(LOG_TAG, "Failed to save a ViewModel for " + parentFragment);
+                        }
+                    }
+                };
+
+        void holderFragmentCreated(Fragment holderFragment) {
+            Fragment parentFragment = holderFragment.getParentFragment();
+            if (parentFragment != null) {
+                mNotCommittedFragmentHolders.remove(parentFragment);
+                parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
+                        mParentDestroyedCallback);
+            } else {
+                mNotCommittedActivityHolders.remove(holderFragment.getActivity());
+            }
+        }
+
+        private static HolderFragment findHolderFragment(FragmentManager manager) {
+            if (manager.isDestroyed()) {
+                throw new IllegalStateException("Can't access ViewModels from onDestroy");
+            }
+
+            Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
+            if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
+                throw new IllegalStateException("Unexpected "
+                        + "fragment instance was returned by HOLDER_TAG");
+            }
+            return (HolderFragment) fragmentByTag;
+        }
+
+        private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
+            HolderFragment holder = new HolderFragment();
+            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
+            return holder;
+        }
+
+        HolderFragment holderFragmentFor(FragmentActivity activity) {
+            FragmentManager fm = activity.getSupportFragmentManager();
+            HolderFragment holder = findHolderFragment(fm);
+            if (holder != null) {
+                return holder;
+            }
+            holder = mNotCommittedActivityHolders.get(activity);
+            if (holder != null) {
+                return holder;
+            }
+
+            if (!mActivityCallbacksIsAdded) {
+                mActivityCallbacksIsAdded = true;
+                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
+            }
+            holder = createHolderFragment(fm);
+            mNotCommittedActivityHolders.put(activity, holder);
+            return holder;
+        }
+
+        HolderFragment holderFragmentFor(Fragment parentFragment) {
+            FragmentManager fm = parentFragment.getChildFragmentManager();
+            HolderFragment holder = findHolderFragment(fm);
+            if (holder != null) {
+                return holder;
+            }
+            holder = mNotCommittedFragmentHolders.get(parentFragment);
+            if (holder != null) {
+                return holder;
+            }
+
+            parentFragment.getFragmentManager()
+                    .registerFragmentLifecycleCallbacks(mParentDestroyedCallback, false);
+            holder = createHolderFragment(fm);
+            mNotCommittedFragmentHolders.put(parentFragment, holder);
+            return holder;
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/LifecycleDispatcher.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/LifecycleDispatcher.java
new file mode 100644
index 0000000..f241276
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/LifecycleDispatcher.java
@@ -0,0 +1,183 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.State.CREATED;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle.State;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * When initialized, it hooks into the Activity callback of the Application and observes
+ * Activities. It is responsible to hook in child-fragments to activities and fragments to report
+ * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle
+ * providers related to an activity as soon it is not safe to run a fragment transaction in this
+ * activity.
+ */
+class LifecycleDispatcher {
+
+    private static final String REPORT_FRAGMENT_TAG = "androidx.lifecycle"
+            + ".LifecycleDispatcher.report_fragment_tag";
+
+    private static AtomicBoolean sInitialized = new AtomicBoolean(false);
+
+    static void init(Context context) {
+        if (sInitialized.getAndSet(true)) {
+            return;
+        }
+        ((Application) context.getApplicationContext())
+                .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
+        private final FragmentCallback mFragmentCallback;
+
+        DispatcherActivityCallback() {
+            mFragmentCallback = new FragmentCallback();
+        }
+
+        @Override
+        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+            if (activity instanceof FragmentActivity) {
+                ((FragmentActivity) activity).getSupportFragmentManager()
+                        .registerFragmentLifecycleCallbacks(mFragmentCallback, true);
+            }
+            ReportFragment.injectIfNeededIn(activity);
+        }
+
+        @Override
+        public void onActivityStopped(Activity activity) {
+            if (activity instanceof FragmentActivity) {
+                markState((FragmentActivity) activity, CREATED);
+            }
+        }
+
+        @Override
+        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+            if (activity instanceof FragmentActivity) {
+                markState((FragmentActivity) activity, CREATED);
+            }
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    public static class DestructionReportFragment extends Fragment {
+        @Override
+        public void onPause() {
+            super.onPause();
+            dispatch(ON_PAUSE);
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+            dispatch(ON_STOP);
+        }
+
+        @Override
+        public void onDestroy() {
+            super.onDestroy();
+            dispatch(ON_DESTROY);
+        }
+
+        protected void dispatch(Lifecycle.Event event) {
+            dispatchIfLifecycleOwner(getParentFragment(), event);
+        }
+    }
+
+    private static void markState(FragmentManager manager, State state) {
+        Collection<Fragment> fragments = manager.getFragments();
+        if (fragments == null) {
+            return;
+        }
+        for (Fragment fragment : fragments) {
+            if (fragment == null) {
+                continue;
+            }
+            markStateIn(fragment, state);
+            if (fragment.isAdded()) {
+                markState(fragment.getChildFragmentManager(), state);
+            }
+        }
+    }
+
+    private static void markStateIn(Object object, State state) {
+        if (object instanceof LifecycleRegistryOwner) {
+            LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle();
+            registry.markState(state);
+        }
+    }
+
+    private static void markState(FragmentActivity activity, State state) {
+        markStateIn(activity, state);
+        markState(activity.getSupportFragmentManager(), state);
+    }
+
+    private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) {
+        if (fragment instanceof LifecycleRegistryOwner) {
+            ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks {
+
+        @Override
+        public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
+            dispatchIfLifecycleOwner(f, ON_CREATE);
+
+            if (!(f instanceof LifecycleRegistryOwner)) {
+                return;
+            }
+
+            if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
+                f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(),
+                        REPORT_FRAGMENT_TAG).commit();
+            }
+        }
+
+        @Override
+        public void onFragmentStarted(FragmentManager fm, Fragment f) {
+            dispatchIfLifecycleOwner(f, ON_START);
+        }
+
+        @Override
+        public void onFragmentResumed(FragmentManager fm, Fragment f) {
+            dispatchIfLifecycleOwner(f, ON_RESUME);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/LifecycleService.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/LifecycleService.java
new file mode 100644
index 0000000..8275573
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/LifecycleService.java
@@ -0,0 +1,77 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.Nullable;
+
+/**
+ * A Service that is also a {@link LifecycleOwner}.
+ */
+public class LifecycleService extends Service implements LifecycleOwner {
+
+    private final ServiceLifecycleDispatcher mDispatcher = new ServiceLifecycleDispatcher(this);
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        mDispatcher.onServicePreSuperOnCreate();
+        super.onCreate();
+    }
+
+    @CallSuper
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        mDispatcher.onServicePreSuperOnBind();
+        return null;
+    }
+
+    @SuppressWarnings("deprecation")
+    @CallSuper
+    @Override
+    public void onStart(Intent intent, int startId) {
+        mDispatcher.onServicePreSuperOnStart();
+        super.onStart(intent, startId);
+    }
+
+    // this method is added only to annotate it with @CallSuper.
+    // In usual service super.onStartCommand is no-op, but in LifecycleService
+    // it results in mDispatcher.onServicePreSuperOnStart() call, because
+    // super.onStartCommand calls onStart().
+    @CallSuper
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @CallSuper
+    @Override
+    public void onDestroy() {
+        mDispatcher.onServicePreSuperOnDestroy();
+        super.onDestroy();
+    }
+
+    @Override
+    public Lifecycle getLifecycle() {
+        return mDispatcher.getLifecycle();
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java
new file mode 100644
index 0000000..73513eb
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java
@@ -0,0 +1,181 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.ReportFragment.ActivityInitializationListener;
+
+/**
+ * Class that provides lifecycle for the whole application process.
+ * <p>
+ * You can consider this LifecycleOwner as the composite of all of your Activities, except that
+ * {@link Lifecycle.Event#ON_CREATE} will be dispatched once and {@link Lifecycle.Event#ON_DESTROY}
+ * will never be dispatched. Other lifecycle events will be dispatched with following rules:
+ * ProcessLifecycleOwner will dispatch {@link Lifecycle.Event#ON_START},
+ * {@link Lifecycle.Event#ON_RESUME} events, as a first activity moves through these events.
+ * {@link Lifecycle.Event#ON_PAUSE}, {@link Lifecycle.Event#ON_STOP}, events will be dispatched with
+ * a <b>delay</b> after a last activity
+ * passed through them. This delay is long enough to guarantee that ProcessLifecycleOwner
+ * won't send any events if activities are destroyed and recreated due to a
+ * configuration change.
+ *
+ * <p>
+ * It is useful for use cases where you would like to react on your app coming to the foreground or
+ * going to the background and you don't need a milliseconds accuracy in receiving lifecycle
+ * events.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ProcessLifecycleOwner implements LifecycleOwner {
+
+    @VisibleForTesting
+    static final long TIMEOUT_MS = 700; //mls
+
+    // ground truth counters
+    private int mStartedCounter = 0;
+    private int mResumedCounter = 0;
+
+    private boolean mPauseSent = true;
+    private boolean mStopSent = true;
+
+    private Handler mHandler;
+    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+
+    private Runnable mDelayedPauseRunnable = new Runnable() {
+        @Override
+        public void run() {
+            dispatchPauseIfNeeded();
+            dispatchStopIfNeeded();
+        }
+    };
+
+    private ActivityInitializationListener mInitializationListener =
+            new ActivityInitializationListener() {
+                @Override
+                public void onCreate() {
+                }
+
+                @Override
+                public void onStart() {
+                    activityStarted();
+                }
+
+                @Override
+                public void onResume() {
+                    activityResumed();
+                }
+            };
+
+    private static final ProcessLifecycleOwner sInstance = new ProcessLifecycleOwner();
+
+    /**
+     * The LifecycleOwner for the whole application process. Note that if your application
+     * has multiple processes, this provider does not know about other processes.
+     *
+     * @return {@link LifecycleOwner} for the whole application.
+     */
+    public static LifecycleOwner get() {
+        return sInstance;
+    }
+
+    static void init(Context context) {
+        sInstance.attach(context);
+    }
+
+    void activityStarted() {
+        mStartedCounter++;
+        if (mStartedCounter == 1 && mStopSent) {
+            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+            mStopSent = false;
+        }
+    }
+
+    void activityResumed() {
+        mResumedCounter++;
+        if (mResumedCounter == 1) {
+            if (mPauseSent) {
+                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+                mPauseSent = false;
+            } else {
+                mHandler.removeCallbacks(mDelayedPauseRunnable);
+            }
+        }
+    }
+
+    void activityPaused() {
+        mResumedCounter--;
+        if (mResumedCounter == 0) {
+            mHandler.postDelayed(mDelayedPauseRunnable, TIMEOUT_MS);
+        }
+    }
+
+    void activityStopped() {
+        mStartedCounter--;
+        dispatchStopIfNeeded();
+    }
+
+    private void dispatchPauseIfNeeded() {
+        if (mResumedCounter == 0) {
+            mPauseSent = true;
+            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
+        }
+    }
+
+    private void dispatchStopIfNeeded() {
+        if (mStartedCounter == 0 && mPauseSent) {
+            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+            mStopSent = true;
+        }
+    }
+
+    private ProcessLifecycleOwner() {
+    }
+
+    void attach(Context context) {
+        mHandler = new Handler();
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        Application app = (Application) context.getApplicationContext();
+        app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
+            @Override
+            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                ReportFragment.get(activity).setProcessListener(mInitializationListener);
+            }
+
+            @Override
+            public void onActivityPaused(Activity activity) {
+                activityPaused();
+            }
+
+            @Override
+            public void onActivityStopped(Activity activity) {
+                activityStopped();
+            }
+        });
+    }
+
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mRegistry;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/ProcessLifecycleOwnerInitializer.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/ProcessLifecycleOwnerInitializer.java
new file mode 100644
index 0000000..e20eca0
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/ProcessLifecycleOwnerInitializer.java
@@ -0,0 +1,69 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Internal class to initialize Lifecycles.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ProcessLifecycleOwnerInitializer extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        LifecycleDispatcher.init(getContext());
+        ProcessLifecycleOwner.init(getContext());
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(@NonNull Uri uri, String[] strings, String s, String[] strings1,
+            String s1) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
+        return null;
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, String s, String[] strings) {
+        return 0;
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, ContentValues contentValues, String s, String[] strings) {
+        return 0;
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.java
new file mode 100644
index 0000000..23d296e
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.java
@@ -0,0 +1,109 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Helper class to dispatch lifecycle events for a service. Use it only if it is impossible
+ * to use {@link LifecycleService}.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ServiceLifecycleDispatcher {
+    private final LifecycleRegistry mRegistry;
+    private final Handler mHandler;
+    private DispatchRunnable mLastDispatchRunnable;
+
+    /**
+     * @param provider {@link LifecycleOwner} for a service, usually it is a service itself
+     */
+    public ServiceLifecycleDispatcher(@NonNull LifecycleOwner provider) {
+        mRegistry = new LifecycleRegistry(provider);
+        mHandler = new Handler();
+    }
+
+    private void postDispatchRunnable(Lifecycle.Event event) {
+        if (mLastDispatchRunnable != null) {
+            mLastDispatchRunnable.run();
+        }
+        mLastDispatchRunnable = new DispatchRunnable(mRegistry, event);
+        mHandler.postAtFrontOfQueue(mLastDispatchRunnable);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onCreate()} method, even before super.onCreate call.
+     */
+    public void onServicePreSuperOnCreate() {
+        postDispatchRunnable(Lifecycle.Event.ON_CREATE);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onBind(Intent)} method, even before super.onBind
+     * call.
+     */
+    public void onServicePreSuperOnBind() {
+        postDispatchRunnable(Lifecycle.Event.ON_START);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onStart(Intent, int)} or
+     * {@link Service#onStartCommand(Intent, int, int)} methods, even before
+     * a corresponding super call.
+     */
+    public void onServicePreSuperOnStart() {
+        postDispatchRunnable(Lifecycle.Event.ON_START);
+    }
+
+    /**
+     * Must be a first call in {@link Service#onDestroy()} method, even before super.OnDestroy
+     * call.
+     */
+    public void onServicePreSuperOnDestroy() {
+        postDispatchRunnable(Lifecycle.Event.ON_STOP);
+        postDispatchRunnable(Lifecycle.Event.ON_DESTROY);
+    }
+
+    /**
+     * @return {@link Lifecycle} for the given {@link LifecycleOwner}
+     */
+    public Lifecycle getLifecycle() {
+        return mRegistry;
+    }
+
+    static class DispatchRunnable implements Runnable {
+        private final LifecycleRegistry mRegistry;
+        final Lifecycle.Event mEvent;
+        private boolean mWasExecuted = false;
+
+        DispatchRunnable(@NonNull LifecycleRegistry registry, Lifecycle.Event event) {
+            mRegistry = registry;
+            mEvent = event;
+        }
+
+        @Override
+        public void run() {
+            if (!mWasExecuted) {
+                mRegistry.handleLifecycleEvent(mEvent);
+                mWasExecuted = true;
+            }
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/ViewModelProviders.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/ViewModelProviders.java
new file mode 100644
index 0000000..ad72a54
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/ViewModelProviders.java
@@ -0,0 +1,150 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.app.Activity;
+import android.app.Application;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProvider.Factory;
+
+/**
+ * Utilities methods for {@link ViewModelStore} class.
+ */
+public class ViewModelProviders {
+
+    /**
+     * @deprecated This class should not be directly instantiated
+     */
+    @Deprecated
+    public ViewModelProviders() {
+    }
+
+    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}.
+     * <p>
+     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
+     *
+     * @param fragment a fragment, in whose scope ViewModels should be retained
+     * @return a ViewModelProvider instance
+     */
+    @NonNull
+    @MainThread
+    public static ViewModelProvider of(@NonNull Fragment fragment) {
+        return of(fragment, null);
+    }
+
+    /**
+     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
+     * is alive. More detailed explanation is in {@link ViewModel}.
+     * <p>
+     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
+     *
+     * @param activity an activity, in whose scope ViewModels should be retained
+     * @return a ViewModelProvider instance
+     */
+    @NonNull
+    @MainThread
+    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
+        return of(activity, null);
+    }
+
+    /**
+     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
+     * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
+     * <p>
+     * It uses the given {@link Factory} to instantiate new ViewModels.
+     *
+     * @param fragment a fragment, in whose scope ViewModels should be retained
+     * @param factory  a {@code Factory} to instantiate new ViewModels
+     * @return a ViewModelProvider instance
+     */
+    @NonNull
+    @MainThread
+    public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
+        Application application = checkApplication(checkActivity(fragment));
+        if (factory == null) {
+            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
+        }
+        return new ViewModelProvider(ViewModelStores.of(fragment), factory);
+    }
+
+    /**
+     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
+     * is alive. More detailed explanation is in {@link ViewModel}.
+     * <p>
+     * It uses the given {@link Factory} to instantiate new ViewModels.
+     *
+     * @param activity an activity, in whose scope ViewModels should be retained
+     * @param factory  a {@code Factory} to instantiate new ViewModels
+     * @return a ViewModelProvider instance
+     */
+    @NonNull
+    @MainThread
+    public static ViewModelProvider of(@NonNull FragmentActivity activity,
+            @Nullable Factory factory) {
+        Application application = checkApplication(activity);
+        if (factory == null) {
+            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
+        }
+        return new ViewModelProvider(ViewModelStores.of(activity), factory);
+    }
+
+    /**
+     * {@link Factory} which may create {@link AndroidViewModel} and
+     * {@link ViewModel}, which have an empty constructor.
+     *
+     * @deprecated Use {@link ViewModelProvider.AndroidViewModelFactory}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @Deprecated
+    public static class DefaultFactory extends ViewModelProvider.AndroidViewModelFactory {
+        /**
+         * Creates a {@code AndroidViewModelFactory}
+         *
+         * @param application an application to pass in {@link AndroidViewModel}
+         * @deprecated Use {@link ViewModelProvider.AndroidViewModelFactory} or
+         * {@link ViewModelProvider.AndroidViewModelFactory#getInstance(Application)}.
+         */
+        @Deprecated
+        public DefaultFactory(@NonNull Application application) {
+            super(application);
+        }
+    }
+}
diff --git a/lifecycle/extensions/src/main/java/androidx/lifecycle/ViewModelStores.java b/lifecycle/extensions/src/main/java/androidx/lifecycle/ViewModelStores.java
new file mode 100644
index 0000000..6627239
--- /dev/null
+++ b/lifecycle/extensions/src/main/java/androidx/lifecycle/ViewModelStores.java
@@ -0,0 +1,64 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.HolderFragment.holderFragmentFor;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * Factory methods for {@link ViewModelStore} class.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ViewModelStores {
+
+    private ViewModelStores() {
+    }
+
+    /**
+     * Returns the {@link ViewModelStore} of the given activity.
+     *
+     * @param activity an activity whose {@code ViewModelStore} is requested
+     * @return a {@code ViewModelStore}
+     */
+    @NonNull
+    @MainThread
+    public static ViewModelStore of(@NonNull FragmentActivity activity) {
+        if (activity instanceof ViewModelStoreOwner) {
+            return ((ViewModelStoreOwner) activity).getViewModelStore();
+        }
+        return holderFragmentFor(activity).getViewModelStore();
+    }
+
+    /**
+     * Returns the {@link ViewModelStore} of the given fragment.
+     *
+     * @param fragment a fragment whose {@code ViewModelStore} is requested
+     * @return a {@code ViewModelStore}
+     */
+    @NonNull
+    @MainThread
+    public static ViewModelStore of(@NonNull Fragment fragment) {
+        if (fragment instanceof ViewModelStoreOwner) {
+            return ((ViewModelStoreOwner) fragment).getViewModelStore();
+        }
+        return holderFragmentFor(fragment).getViewModelStore();
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/DispatcherActivityCallbackTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/DispatcherActivityCallbackTest.java
deleted file mode 100644
index 86b25b6..0000000
--- a/lifecycle/extensions/src/test/java/android/arch/lifecycle/DispatcherActivityCallbackTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class DispatcherActivityCallbackTest {
-    @Test
-    public void onCreateFrameworkActivity() {
-        LifecycleDispatcher.DispatcherActivityCallback callback =
-                new LifecycleDispatcher.DispatcherActivityCallback();
-        Activity activity = mock(Activity.class);
-        checkReportFragment(callback, activity);
-    }
-
-    @Test
-    public void onCreateFragmentActivity() {
-        LifecycleDispatcher.DispatcherActivityCallback callback =
-                new LifecycleDispatcher.DispatcherActivityCallback();
-        FragmentActivity activity = mock(FragmentActivity.class);
-        FragmentManager fragmentManager = mock(FragmentManager.class);
-        when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
-
-        checkReportFragment(callback, activity);
-
-        verify(activity).getSupportFragmentManager();
-        verify(fragmentManager).registerFragmentLifecycleCallbacks(
-                any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true));
-    }
-
-    @SuppressLint("CommitTransaction")
-    private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback,
-            Activity activity) {
-        android.app.FragmentManager fm = mock(android.app.FragmentManager.class);
-        FragmentTransaction transaction = mock(FragmentTransaction.class);
-        when(activity.getFragmentManager()).thenReturn(fm);
-        when(fm.beginTransaction()).thenReturn(transaction);
-        when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction);
-        callback.onActivityCreated(activity, mock(Bundle.class));
-        verify(activity).getFragmentManager();
-        verify(fm).beginTransaction();
-        verify(transaction).add(any(ReportFragment.class), anyString());
-        verify(transaction).commit();
-    }
-}
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelProvidersTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelProvidersTest.java
deleted file mode 100644
index f37c9a2..0000000
--- a/lifecycle/extensions/src/test/java/android/arch/lifecycle/ViewModelProvidersTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 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.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class ViewModelProvidersTest {
-
-    @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());
-    }
-}
diff --git a/lifecycle/extensions/src/test/java/androidx/lifecycle/DispatcherActivityCallbackTest.java b/lifecycle/extensions/src/test/java/androidx/lifecycle/DispatcherActivityCallbackTest.java
new file mode 100644
index 0000000..dba2488
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/androidx/lifecycle/DispatcherActivityCallbackTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DispatcherActivityCallbackTest {
+    @Test
+    public void onCreateFrameworkActivity() {
+        LifecycleDispatcher.DispatcherActivityCallback callback =
+                new LifecycleDispatcher.DispatcherActivityCallback();
+        Activity activity = mock(Activity.class);
+        checkReportFragment(callback, activity);
+    }
+
+    @Test
+    public void onCreateFragmentActivity() {
+        LifecycleDispatcher.DispatcherActivityCallback callback =
+                new LifecycleDispatcher.DispatcherActivityCallback();
+        FragmentActivity activity = mock(FragmentActivity.class);
+        FragmentManager fragmentManager = mock(FragmentManager.class);
+        when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
+
+        checkReportFragment(callback, activity);
+
+        verify(activity).getSupportFragmentManager();
+        verify(fragmentManager).registerFragmentLifecycleCallbacks(
+                any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true));
+    }
+
+    @SuppressLint("CommitTransaction")
+    private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback,
+            Activity activity) {
+        android.app.FragmentManager fm = mock(android.app.FragmentManager.class);
+        FragmentTransaction transaction = mock(FragmentTransaction.class);
+        when(activity.getFragmentManager()).thenReturn(fm);
+        when(fm.beginTransaction()).thenReturn(transaction);
+        when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction);
+        callback.onActivityCreated(activity, mock(Bundle.class));
+        verify(activity).getFragmentManager();
+        verify(fm).beginTransaction();
+        verify(transaction).add(any(ReportFragment.class), anyString());
+        verify(transaction).commit();
+    }
+}
diff --git a/lifecycle/extensions/src/test/java/androidx/lifecycle/ViewModelProvidersTest.java b/lifecycle/extensions/src/test/java/androidx/lifecycle/ViewModelProvidersTest.java
new file mode 100644
index 0000000..e21f5c1
--- /dev/null
+++ b/lifecycle/extensions/src/test/java/androidx/lifecycle/ViewModelProvidersTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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 androidx.lifecycle;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ViewModelProvidersTest {
+
+    @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());
+    }
+}
diff --git a/lifecycle/integration-tests/test-app/src/main/java/com/android/support/lifecycle/testapp/LiveDataTestActivity.java b/lifecycle/integration-tests/test-app/src/main/java/com/android/support/lifecycle/testapp/LiveDataTestActivity.java
index 5bbd368..7b94407 100644
--- a/lifecycle/integration-tests/test-app/src/main/java/com/android/support/lifecycle/testapp/LiveDataTestActivity.java
+++ b/lifecycle/integration-tests/test-app/src/main/java/com/android/support/lifecycle/testapp/LiveDataTestActivity.java
@@ -17,7 +17,8 @@
 package com.android.support.lifecycle.testapp;
 
 import android.os.Bundle;
-import android.support.annotation.Nullable;
+
+import androidx.annotation.Nullable;
 
 import com.android.support.lifecycle.LifecycleActivity;
 import com.android.support.lifecycle.LifecycleFragment;
diff --git a/lifecycle/integration-tests/testapp/build.gradle b/lifecycle/integration-tests/testapp/build.gradle
index c8fba58..a842c45 100644
--- a/lifecycle/integration-tests/testapp/build.gradle
+++ b/lifecycle/integration-tests/testapp/build.gradle
@@ -22,7 +22,7 @@
 
 android {
     defaultConfig {
-        applicationId "android.arch.lifecycle.testapp"
+        applicationId "androidx.lifecycle.testapp"
     }
     buildTypes {
         // test coverage does not work w/ jack
@@ -37,18 +37,18 @@
 
 dependencies {
     // IJ canont figure out transitive dependencies so need to declare them.
-    implementation(project(":lifecycle:common"))
-    implementation(project(":lifecycle:runtime"))
-    implementation(project(":lifecycle:extensions"))
-    annotationProcessor(project(":lifecycle:compiler"))
+    implementation(project(":lifecycle:lifecycle-common"))
+    implementation(project(":lifecycle:lifecycle-runtime"))
+    implementation(project(":lifecycle:lifecycle-extensions"))
+    annotationProcessor(project(":lifecycle:lifecycle-compiler"))
 
-    androidTestAnnotationProcessor(project(":lifecycle:compiler"))
+    androidTestAnnotationProcessor(project(":lifecycle:lifecycle-compiler"))
     androidTestImplementation(TEST_RUNNER)
     androidTestImplementation(ESPRESSO_CORE)
 
     testImplementation(JUNIT)
     testImplementation(MOCKITO_CORE)
-    testAnnotationProcessor(project(":lifecycle:compiler"))
+    testAnnotationProcessor(project(":lifecycle:lifecycle-compiler"))
 }
 
 tasks['check'].dependsOn(tasks['connectedCheck'])
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java
deleted file mode 100644
index 78dd015..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
-import static android.arch.lifecycle.TestUtils.flatMap;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-import android.app.Activity;
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
-import android.arch.lifecycle.testapp.CollectingSupportActivity;
-import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
-import android.arch.lifecycle.testapp.TestEvent;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v4.util.Pair;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(Parameterized.class)
-public class ActivityFullLifecycleTest {
-    @Rule
-    public final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule;
-
-    @Parameterized.Parameters
-    public static Class[] params() {
-        return new Class[]{CollectingSupportActivity.class,
-                FrameworkLifecycleRegistryActivity.class};
-    }
-
-    public ActivityFullLifecycleTest(Class<? extends Activity> activityClass) {
-        //noinspection unchecked
-        activityTestRule = new ActivityTestRule(activityClass);
-    }
-
-
-    @Test
-    public void testFullLifecycle() throws Throwable {
-        CollectingLifecycleOwner owner = activityTestRule.getActivity();
-        TestUtils.waitTillResumed(owner, activityTestRule);
-        activityTestRule.finishActivity();
-
-        TestUtils.waitTillDestroyed(owner, activityTestRule);
-        List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents();
-        assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
deleted file mode 100644
index f48f788..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.TestUtils.recreateActivity;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.iterableWithSize;
-
-import static java.util.Arrays.asList;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.EmptyActivity;
-import android.arch.lifecycle.testapp.R;
-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;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentInBackStackLifecycleTest {
-    @Rule
-    public ActivityTestRule<EmptyActivity> activityTestRule = new ActivityTestRule<>(
-            EmptyActivity.class);
-
-    @Test
-    public void test() throws Throwable {
-        final ArrayList<Event> collectedEvents = new ArrayList<>();
-        LifecycleObserver collectingObserver = new LifecycleObserver() {
-            @OnLifecycleEvent(Event.ON_ANY)
-            void onAny(@SuppressWarnings("unused") LifecycleOwner owner, Event event) {
-                collectedEvents.add(event);
-            }
-        };
-        final FragmentActivity activity = activityTestRule.getActivity();
-        activityTestRule.runOnUiThread(() -> {
-            FragmentManager fm = activity.getSupportFragmentManager();
-            Fragment fragment = new Fragment();
-            fm.beginTransaction().add(R.id.fragment_container, fragment, "tag").addToBackStack(null)
-                    .commit();
-            fm.executePendingTransactions();
-
-            fragment.getLifecycle().addObserver(collectingObserver);
-            Fragment fragment2 = new Fragment();
-            fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
-                    .commit();
-            fm.executePendingTransactions();
-            assertThat(collectedEvents, is(asList(ON_CREATE, ON_START, ON_RESUME,
-                    ON_PAUSE, ON_STOP)));
-            collectedEvents.clear();
-        });
-        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();
-            Fragment fragment = fm.findFragmentByTag("tag");
-            fragment.getLifecycle().addObserver(collectingObserver);
-            assertThat(collectedEvents, iterableWithSize(0));
-            fm.popBackStackImmediate();
-            assertThat(collectedEvents, is(asList(ON_CREATE, ON_START, ON_RESUME)));
-        });
-    }
-
-
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
deleted file mode 100644
index d5ba9df..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.app.Instrumentation;
-import android.arch.lifecycle.testapp.CollectingSupportActivity;
-import android.arch.lifecycle.testapp.CollectingSupportFragment;
-import android.arch.lifecycle.testapp.NavigationDialogActivity;
-import android.content.Intent;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v4.app.FragmentActivity;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LiveDataOnSaveInstanceStateTest {
-    @Rule
-    public ActivityTestRule<CollectingSupportActivity> mActivityTestRule =
-            new ActivityTestRule<>(CollectingSupportActivity.class);
-
-    @Test
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
-    public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
-        CollectingSupportActivity activity = mActivityTestRule.getActivity();
-
-        liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
-    }
-
-    @Test
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
-    public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
-        CollectingSupportActivity activity = mActivityTestRule.getActivity();
-        CollectingSupportFragment fragment = new CollectingSupportFragment();
-        mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
-
-        liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
-    }
-
-    @Test
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
-    public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
-        CollectingSupportActivity activity = mActivityTestRule.getActivity();
-        CollectingSupportFragment fragment = new CollectingSupportFragment();
-        CollectingSupportFragment fragment2 = new CollectingSupportFragment();
-        mActivityTestRule.runOnUiThread(() -> {
-            activity.replaceFragment(fragment);
-            fragment.replaceFragment(fragment2);
-        });
-
-        liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
-    public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
-        CollectingSupportActivity activity = mActivityTestRule.getActivity();
-
-        liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
-    public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
-        CollectingSupportActivity activity = mActivityTestRule.getActivity();
-        CollectingSupportFragment fragment = new CollectingSupportFragment();
-        mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
-
-        liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
-    public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
-        CollectingSupportActivity activity = mActivityTestRule.getActivity();
-        CollectingSupportFragment fragment = new CollectingSupportFragment();
-        CollectingSupportFragment fragment2 = new CollectingSupportFragment();
-        mActivityTestRule.runOnUiThread(() -> {
-            activity.replaceFragment(fragment);
-            fragment.replaceFragment(fragment2);
-        });
-
-        liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
-    }
-
-    private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
-            throws Throwable {
-        final AtomicInteger atomicInteger = new AtomicInteger(0);
-        MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
-        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
-
-        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
-        mActivityTestRule.runOnUiThread(() ->
-                mutableLiveData.observe(lifecycleOwner, atomicInteger::set));
-
-        final FragmentActivity dialogActivity = launchDialog();
-
-        TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule);
-
-        // Change the LiveData value and assert that the observer is not called given that the
-        // lifecycle is in the CREATED state.
-        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
-        assertThat(atomicInteger.get(), is(0));
-
-        // Finish the dialog Activity, wait for the main activity to be resumed, and assert that
-        // the observer's onChanged method is called.
-        mActivityTestRule.runOnUiThread(dialogActivity::finish);
-        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
-        assertThat(atomicInteger.get(), is(1));
-    }
-
-    private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
-            throws Throwable {
-        final AtomicInteger atomicInteger = new AtomicInteger(0);
-        MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
-        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
-
-        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
-
-        mActivityTestRule.runOnUiThread(() ->
-                mutableLiveData.observe(lifecycleOwner, atomicInteger::set));
-
-        // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
-        // lifecycleOwner to hit onPause (or enter the STARTED state).  On API 24 and above, this
-        // onPause should be the last lifecycle method called (and the STARTED state should be the
-        // final resting state).
-        launchDialog();
-        TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
-
-        // Change the LiveData's value and verify that the observer's onChanged method is called
-        // since we are in the STARTED state.
-        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
-        assertThat(atomicInteger.get(), is(1));
-    }
-
-    private FragmentActivity launchDialog() throws Throwable {
-        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
-                NavigationDialogActivity.class.getCanonicalName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-
-        FragmentActivity activity = mActivityTestRule.getActivity();
-        // helps with less flaky API 16 tests
-        Intent intent = new Intent(activity, NavigationDialogActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        activity.startActivity(intent);
-        FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
-        TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule);
-        return fragmentActivity;
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/PartiallyCoveredActivityTest.java
deleted file mode 100644
index 07a9dc5..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/PartiallyCoveredActivityTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
-import static android.arch.lifecycle.TestUtils.flatMap;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-
-import android.app.Instrumentation;
-import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
-import android.arch.lifecycle.testapp.CollectingSupportActivity;
-import android.arch.lifecycle.testapp.CollectingSupportFragment;
-import android.arch.lifecycle.testapp.NavigationDialogActivity;
-import android.arch.lifecycle.testapp.TestEvent;
-import android.content.Intent;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.util.Pair;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-/**
- * Runs tests about the state when an activity is partially covered by another activity. Pre
- * API 24, framework behavior changes so the test rely on whether state is saved or not and makes
- * assertions accordingly.
- */
-@SuppressWarnings("unchecked")
-@RunWith(Parameterized.class)
-@LargeTest
-public class PartiallyCoveredActivityTest {
-    private static final List[] IF_SAVED = new List[]{
-            // when overlaid
-            flatMap(CREATE, START, RESUME, PAUSE,
-                    singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))),
-            // post dialog dismiss
-            asList(new Pair<>(OWNER_CALLBACK, ON_RESUME),
-                    new Pair<>(LIFECYCLE_EVENT, ON_START),
-                    new Pair<>(LIFECYCLE_EVENT, ON_RESUME)),
-            // post finish
-            flatMap(PAUSE, STOP, DESTROY)};
-
-    private static final List[] IF_NOT_SAVED = new List[]{
-            // when overlaid
-            flatMap(CREATE, START, RESUME, PAUSE),
-            // post dialog dismiss
-            flatMap(RESUME),
-            // post finish
-            flatMap(PAUSE, STOP, DESTROY)};
-
-    private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;
-    private static final List<Pair<TestEvent, Lifecycle.Event>>[] EXPECTED =
-            sShouldSave ? IF_SAVED : IF_NOT_SAVED;
-
-    @Rule
-    public ActivityTestRule<CollectingSupportActivity> activityRule =
-            new ActivityTestRule<CollectingSupportActivity>(
-                    CollectingSupportActivity.class) {
-                @Override
-                protected Intent getActivityIntent() {
-                    // helps with less flaky API 16 tests
-                    Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
-                            CollectingSupportActivity.class);
-                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-                    return intent;
-                }
-            };
-    private final boolean mDismissDialog;
-
-    @Parameterized.Parameters(name = "dismissDialog_{0}")
-    public static List<Boolean> dismissDialog() {
-        return asList(true, false);
-    }
-
-    public PartiallyCoveredActivityTest(boolean dismissDialog) {
-        mDismissDialog = dismissDialog;
-    }
-
-    @Test
-    public void coveredWithDialog_activity() throws Throwable {
-        final CollectingSupportActivity activity = activityRule.getActivity();
-        runTest(activity);
-    }
-
-    @Test
-    public void coveredWithDialog_fragment() throws Throwable {
-        CollectingSupportFragment fragment = new CollectingSupportFragment();
-        activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment));
-        runTest(fragment);
-    }
-
-    @Test
-    public void coveredWithDialog_childFragment() throws Throwable {
-        CollectingSupportFragment parentFragment = new CollectingSupportFragment();
-        CollectingSupportFragment childFragment = new CollectingSupportFragment();
-        activityRule.runOnUiThread(() -> {
-            activityRule.getActivity().replaceFragment(parentFragment);
-            parentFragment.replaceFragment(childFragment);
-        });
-        runTest(childFragment);
-    }
-
-    private void runTest(CollectingLifecycleOwner owner) throws Throwable {
-        TestUtils.waitTillResumed(owner, activityRule);
-        FragmentActivity dialog = launchDialog();
-        assertStateSaving();
-        waitForIdle();
-        assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
-        List<Pair<TestEvent, Lifecycle.Event>> expected;
-        if (mDismissDialog) {
-            dialog.finish();
-            TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
-            assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
-            expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
-        } else {
-            expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
-        }
-        CollectingSupportActivity activity = activityRule.getActivity();
-        activityRule.finishActivity();
-        TestUtils.waitTillDestroyed(activity, activityRule);
-        assertThat(owner.copyCollectedEvents(), is(expected));
-    }
-
-    // test sanity
-    private void assertStateSaving() throws ExecutionException, InterruptedException {
-        final CollectingSupportActivity activity = activityRule.getActivity();
-        if (sShouldSave) {
-            // state should be saved. wait for it to be saved
-            assertThat("test sanity",
-                    activity.waitForStateSave(20), is(true));
-            assertThat("test sanity", activity.getSupportFragmentManager()
-                    .isStateSaved(), is(true));
-        } else {
-            // should should not be saved
-            assertThat("test sanity", activity.getSupportFragmentManager()
-                    .isStateSaved(), is(false));
-        }
-    }
-
-    private void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
-    private FragmentActivity launchDialog() throws Throwable {
-        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
-                NavigationDialogActivity.class.getCanonicalName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-
-        FragmentActivity activity = activityRule.getActivity();
-
-        Intent intent = new Intent(activity, NavigationDialogActivity.class);
-        // disabling animations helps with less flaky API 16 tests
-        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        activity.startActivity(intent);
-        FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
-        TestUtils.waitTillResumed(fragmentActivity, activityRule);
-        return fragmentActivity;
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ProcessOwnerTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ProcessOwnerTest.java
deleted file mode 100644
index 77baf94..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/ProcessOwnerTest.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_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.TestUtils.waitTillResumed;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-
-import android.app.Instrumentation;
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.NavigationDialogActivity;
-import android.arch.lifecycle.testapp.NavigationTestActivityFirst;
-import android.arch.lifecycle.testapp.NavigationTestActivitySecond;
-import android.arch.lifecycle.testapp.NonSupportActivity;
-import android.content.Context;
-import android.content.Intent;
-import android.support.test.InstrumentationRegistry;
-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;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ProcessOwnerTest {
-
-    @Rule
-    public ActivityTestRule<NavigationTestActivityFirst> activityTestRule =
-            new ActivityTestRule<>(NavigationTestActivityFirst.class);
-
-    static class ProcessObserver implements LifecycleObserver {
-        volatile boolean mChangedState;
-
-        @OnLifecycleEvent(Event.ON_ANY)
-        void onEvent() {
-            mChangedState = true;
-        }
-    }
-
-    private ProcessObserver mObserver = new ProcessObserver();
-
-    @After
-    public void tearDown() {
-        try {
-            // reassure that our observer is removed.
-            removeProcessObserver(mObserver);
-        } catch (Throwable throwable) {
-            throwable.printStackTrace();
-        }
-    }
-
-    @Test
-    public void testNavigation() throws Throwable {
-        FragmentActivity firstActivity = setupObserverOnResume();
-        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
-                NavigationTestActivitySecond.class.getCanonicalName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-
-        Intent intent = new Intent(firstActivity, NavigationTestActivitySecond.class);
-        firstActivity.finish();
-        firstActivity.startActivity(intent);
-
-        FragmentActivity secondActivity = (FragmentActivity) monitor.waitForActivity();
-        assertThat("Failed to navigate", secondActivity, notNullValue());
-        checkProcessObserverSilent(secondActivity);
-    }
-
-    @Test
-    public void testNavigationToNonSupport() throws Throwable {
-        FragmentActivity firstActivity = setupObserverOnResume();
-        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
-                NonSupportActivity.class.getCanonicalName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-
-        Intent intent = new Intent(firstActivity, NonSupportActivity.class);
-        firstActivity.finish();
-        firstActivity.startActivity(intent);
-        NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity();
-        assertThat("Failed to navigate", secondActivity, notNullValue());
-        checkProcessObserverSilent(secondActivity);
-    }
-
-    @Test
-    public void testRecreation() throws Throwable {
-        FragmentActivity activity = setupObserverOnResume();
-        FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
-        assertThat("Failed to recreate", recreated, notNullValue());
-        checkProcessObserverSilent(recreated);
-    }
-
-    @Test
-    public void testPressHomeButton() throws Throwable {
-        setupObserverOnResume();
-
-        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
-                NavigationDialogActivity.class.getCanonicalName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-
-        NavigationTestActivityFirst activity = activityTestRule.getActivity();
-        activity.startActivity(new Intent(activity, NavigationDialogActivity.class));
-        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(@SuppressWarnings("unused") LifecycleOwner provider,
-                    Event event) {
-                events.add(event);
-            }
-        };
-        addProcessObserver(collectingObserver);
-        events.clear();
-        assertThat(activity.moveTaskToBack(true), is(true));
-        Thread.sleep(ProcessLifecycleOwner.TIMEOUT_MS * 2);
-        assertThat(events.toArray(), is(new Event[]{ON_PAUSE, ON_STOP}));
-        events.clear();
-        Context context = InstrumentationRegistry.getContext();
-        context.startActivity(new Intent(activity, NavigationDialogActivity.class)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-        waitTillResumed(dialogActivity, activityTestRule);
-        assertThat(events.toArray(), is(new Event[]{ON_START, ON_RESUME}));
-        removeProcessObserver(collectingObserver);
-        dialogActivity.finish();
-    }
-
-    private FragmentActivity setupObserverOnResume() throws Throwable {
-        FragmentActivity firstActivity = activityTestRule.getActivity();
-        waitTillResumed(firstActivity, activityTestRule);
-        addProcessObserver(mObserver);
-        mObserver.mChangedState = false;
-        return firstActivity;
-    }
-
-    private void addProcessObserver(LifecycleObserver observer) throws Throwable {
-        activityTestRule.runOnUiThread(() ->
-                ProcessLifecycleOwner.get().getLifecycle().addObserver(observer));
-    }
-
-    private void removeProcessObserver(LifecycleObserver observer) throws Throwable {
-        activityTestRule.runOnUiThread(() ->
-                ProcessLifecycleOwner.get().getLifecycle().removeObserver(observer));
-    }
-
-    private void checkProcessObserverSilent(FragmentActivity activity) throws Throwable {
-        waitTillResumed(activity, activityTestRule);
-        assertThat(mObserver.mChangedState, is(false));
-        activityTestRule.runOnUiThread(() ->
-                ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
-    }
-
-    private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable {
-        assertThat(activity.awaitResumedState(), is(true));
-        assertThat(mObserver.mChangedState, is(false));
-        activityTestRule.runOnUiThread(() ->
-                ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SimpleAppFullLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SimpleAppFullLifecycleTest.java
deleted file mode 100644
index 44d1e8a..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SimpleAppFullLifecycleTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.CREATED;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.isIn;
-import static org.hamcrest.Matchers.notNullValue;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.Lifecycle.State;
-import android.arch.lifecycle.testapp.SimpleAppLifecycleTestActivity;
-import android.arch.lifecycle.testapp.SimpleAppLifecycleTestActivity.TestEventType;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Pair;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class SimpleAppFullLifecycleTest {
-
-    @SuppressWarnings("unchecked")
-    private static final Pair[] EXPECTED_EVENTS_CONSTRUCTION =
-            new Pair[] {
-                new Pair(TestEventType.PROCESS_EVENT, Event.ON_CREATE),
-                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_CREATE),
-                new Pair(TestEventType.PROCESS_EVENT, Event.ON_START),
-                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_START),
-                new Pair(TestEventType.PROCESS_EVENT, Event.ON_RESUME),
-                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_RESUME),
-            };
-
-    @SuppressWarnings("unchecked")
-    private static final Pair[] EXPECTED_EVENTS_DESTRUCTION =
-            new Pair[]{
-
-                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_PAUSE),
-                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_STOP),
-                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_DESTROY),
-
-                    new Pair(TestEventType.PROCESS_EVENT, Event.ON_PAUSE),
-                    new Pair(TestEventType.PROCESS_EVENT, Event.ON_STOP),
-            };
-    @Rule
-    public ActivityTestRule<SimpleAppLifecycleTestActivity> activityTestRule =
-            new ActivityTestRule<>(SimpleAppLifecycleTestActivity.class, false, false);
-
-    @Before
-    public void setup() {
-        // cool down period, so application state will become DESTROYED
-        try {
-            Thread.sleep(ProcessLifecycleOwner.TIMEOUT_MS * 2);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        SimpleAppLifecycleTestActivity.startProcessObserver();
-    }
-
-    @After
-    public void tearDown() {
-        SimpleAppLifecycleTestActivity.stopProcessObserver();
-    }
-
-    @Test
-    public void testFullLifecycle() throws InterruptedException {
-        State currentState = ProcessLifecycleOwner.get().getLifecycle().getCurrentState();
-        assertThat(currentState, is(CREATED));
-        activityTestRule.launchActivity(null);
-        List<Pair<TestEventType, Event>> events = SimpleAppLifecycleTestActivity.awaitForEvents();
-        assertThat("Failed to await for events", events, notNullValue());
-        //noinspection ConstantConditions
-        assertThat(events.subList(0, 6).toArray(), is(EXPECTED_EVENTS_CONSTRUCTION));
-
-        // TODO: bug 35122523
-        for (Pair<TestEventType, Event> event: events.subList(6, 11)) {
-            assertThat(event, isIn(EXPECTED_EVENTS_DESTRUCTION));
-        }
-    }
-
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SynchronousActivityLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SynchronousActivityLifecycleTest.java
deleted file mode 100644
index 4b91363..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/SynchronousActivityLifecycleTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-import android.app.Activity;
-import android.app.Application;
-import android.app.Instrumentation;
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.LifecycleTestActivity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.os.Build;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.UiThreadTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Method;
-
-/**
- * It tests that an event is dispatched immediately after a call of corresponding OnXXX method
- * during an execution of performXXX
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class SynchronousActivityLifecycleTest {
-
-    @Rule
-    public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
-
-    @Test
-    public void testOnCreateCall() throws Throwable {
-        testSynchronousCall(Event.ON_CREATE,
-                activity -> {
-                },
-                activity -> getInstrumentation().callActivityOnCreate(activity, null));
-    }
-
-    @Test
-    public void testOnStartCall() throws Throwable {
-        testSynchronousCall(Lifecycle.Event.ON_START,
-                activity -> getInstrumentation().callActivityOnCreate(activity, null),
-                SynchronousActivityLifecycleTest::performStart);
-    }
-
-    @Test
-    public void testOnResumeCall() throws Throwable {
-        testSynchronousCall(Lifecycle.Event.ON_RESUME,
-                activity -> {
-                    getInstrumentation().callActivityOnCreate(activity, null);
-                    performStart(activity);
-                },
-                SynchronousActivityLifecycleTest::performResume);
-    }
-
-    @Test
-    public void testOnStopCall() throws Throwable {
-        testSynchronousCall(Lifecycle.Event.ON_STOP,
-                activity -> {
-                    getInstrumentation().callActivityOnCreate(activity, null);
-                    performStart(activity);
-                },
-                SynchronousActivityLifecycleTest::performStop);
-    }
-
-    @Test
-    public void testOnDestroyCall() throws Throwable {
-        testSynchronousCall(Lifecycle.Event.ON_DESTROY,
-                activity -> getInstrumentation().callActivityOnCreate(activity, null),
-                activity -> getInstrumentation().callActivityOnDestroy(activity));
-    }
-
-    public void testSynchronousCall(Event event, ActivityCall preInit, ActivityCall call)
-            throws Throwable {
-        uiThreadTestRule.runOnUiThread(() -> {
-            Intent intent = new Intent();
-            ComponentName cn = new ComponentName(LifecycleTestActivity.class.getPackage().getName(),
-                    LifecycleTestActivity.class.getName());
-            intent.setComponent(cn);
-            Instrumentation instrumentation = getInstrumentation();
-            try {
-                Application app =
-                        (Application) instrumentation.getTargetContext().getApplicationContext();
-                LifecycleTestActivity testActivity =
-                        (LifecycleTestActivity) instrumentation.newActivity(
-                                LifecycleTestActivity.class, instrumentation.getTargetContext(),
-                                null, app, intent, new ActivityInfo(), "bla", null, null, null);
-                preInit.call(testActivity);
-                TestObserver testObserver = new TestObserver(testActivity, event);
-                testActivity.getLifecycle().addObserver(testObserver);
-                testObserver.unmute();
-                call.call(testActivity);
-
-                assertThat(testObserver.mEventReceived, is(true));
-            } catch (Exception e) {
-                throw new Error(e);
-            }
-        });
-    }
-
-    // Instrumentation.callOnActivityCreate calls performCreate on mActivity,
-    // but Instrumentation.callOnActivityStart calls onStart instead of performStart. ¯\_(ツ)_/¯
-    private static void performStart(Activity activity) {
-        try {
-            Method m = Activity.class.getDeclaredMethod("performStart");
-            m.setAccessible(true);
-            m.invoke(activity);
-        } catch (Exception e) {
-            throw new Error(e);
-        }
-    }
-
-    private static void performResume(Activity activity) {
-        try {
-            Method m = Activity.class.getDeclaredMethod("performResume");
-            m.setAccessible(true);
-            m.invoke(activity);
-        } catch (Exception e) {
-            throw new Error(e);
-        }
-    }
-
-
-    private static void performStop(Activity activity) {
-        try {
-            if (Build.VERSION.SDK_INT >= 24) {
-                Method m = Activity.class.getDeclaredMethod("performStop", boolean.class);
-                m.setAccessible(true);
-                m.invoke(activity, false);
-            } else {
-                Method m = Activity.class.getDeclaredMethod("performStop");
-                m.setAccessible(true);
-                m.invoke(activity);
-            }
-        } catch (Exception e) {
-            throw new Error(e);
-        }
-    }
-
-    private static class TestObserver implements GenericLifecycleObserver {
-        private final LifecycleTestActivity mActivity;
-        private final Event mExpectedEvent;
-        boolean mEventReceived = false;
-        boolean mMuted = true;
-
-        private TestObserver(LifecycleTestActivity activity, Event expectedEvent) {
-            this.mActivity = activity;
-            this.mExpectedEvent = expectedEvent;
-        }
-
-        void unmute() {
-            mMuted = false;
-        }
-
-        @Override
-        public void onStateChanged(LifecycleOwner lifecycleOwner, Event event) {
-            if (mMuted) {
-                return;
-            }
-            assertThat(event, is(mExpectedEvent));
-            assertThat(mActivity.mLifecycleCallFinished, is(true));
-            mEventReceived = true;
-        }
-    }
-
-    private interface ActivityCall {
-        void call(Activity activity);
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java b/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java
deleted file mode 100644
index f7f9bbe..0000000
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/android/arch/lifecycle/TestUtils.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.Lifecycle.State.CREATED;
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.RESUMED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.app.Instrumentation.ActivityMonitor;
-import android.arch.lifecycle.testapp.TestEvent;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v4.util.Pair;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-class TestUtils {
-
-    private static final long TIMEOUT_MS = 2000;
-
-    @SuppressWarnings("unchecked")
-    static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
-            throws Throwable {
-        ActivityMonitor monitor = new ActivityMonitor(
-                activity.getClass().getCanonicalName(), null, false);
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        instrumentation.addMonitor(monitor);
-        rule.runOnUiThread(activity::recreate);
-        T result;
-
-        // this guarantee that we will reinstall monitor between notifications about onDestroy
-        // and onCreate
-        //noinspection SynchronizationOnLocalVariableOrMethodParameter
-        synchronized (monitor) {
-            do {
-                // the documetation says "Block until an Activity is created
-                // that matches this monitor." This statement is true, but there are some other
-                // true statements like: "Block until an Activity is destoyed" or
-                // "Block until an Activity is resumed"...
-
-                // this call will release synchronization monitor's monitor
-                result = (T) monitor.waitForActivityWithTimeout(TIMEOUT_MS);
-                if (result == null) {
-                    throw new RuntimeException("Timeout. Failed to recreate an activity");
-                }
-            } while (result == activity);
-        }
-        return result;
-    }
-
-    static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
-            throws Throwable {
-        waitTillState(owner, activityRule, CREATED);
-    }
-
-    static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
-            throws Throwable {
-        waitTillState(owner, activityRule, STARTED);
-    }
-
-    static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
-            throws Throwable {
-        waitTillState(owner, activityRule, RESUMED);
-    }
-
-    static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
-            throws Throwable {
-        waitTillState(owner, activityRule, DESTROYED);
-    }
-
-    static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
-            Lifecycle.State state)
-            throws Throwable {
-        final CountDownLatch latch = new CountDownLatch(1);
-        activityRule.runOnUiThread(() -> {
-            Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
-            if (currentState == state) {
-                latch.countDown();
-            } else {
-                owner.getLifecycle().addObserver(new LifecycleObserver() {
-                    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-                    public void onStateChanged(LifecycleOwner provider) {
-                        if (provider.getLifecycle().getCurrentState() == state) {
-                            latch.countDown();
-                            provider.getLifecycle().removeObserver(this);
-                        }
-                    }
-                });
-            }
-        });
-        boolean latchResult = latch.await(1, TimeUnit.MINUTES);
-        assertThat("expected " + state + " never happened. Current state:"
-                        + owner.getLifecycle().getCurrentState(), latchResult, is(true));
-
-        // wait for another loop to ensure all observers are called
-        activityRule.runOnUiThread(() -> {
-            // do nothing
-        });
-    }
-
-    @SafeVarargs
-    static <T> List<T> flatMap(List<T>... items) {
-        ArrayList<T> result = new ArrayList<>();
-        for (List<T> item : items) {
-            result.addAll(item);
-        }
-        return result;
-    }
-
-    /**
-     * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
-     * in the order they should arrive.
-     */
-    @SuppressWarnings("unchecked")
-    static class OrderedTuples {
-        static final List<Pair<TestEvent, Lifecycle.Event>> CREATE =
-                Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
-                        new Pair(LIFECYCLE_EVENT, ON_CREATE));
-        static final List<Pair<TestEvent, Lifecycle.Event>> START =
-                Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
-                        new Pair(LIFECYCLE_EVENT, ON_START));
-        static final List<Pair<TestEvent, Lifecycle.Event>> RESUME =
-                Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
-                        new Pair(LIFECYCLE_EVENT, ON_RESUME));
-        static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE =
-                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
-                        new Pair(OWNER_CALLBACK, ON_PAUSE));
-        static final List<Pair<TestEvent, Lifecycle.Event>> STOP =
-                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
-                        new Pair(OWNER_CALLBACK, ON_STOP));
-        static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY =
-                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
-                        new Pair(OWNER_CALLBACK, ON_DESTROY));
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/ActivityFullLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/ActivityFullLifecycleTest.java
new file mode 100644
index 0000000..a5a6f0f
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/ActivityFullLifecycleTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static androidx.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static androidx.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static androidx.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static androidx.lifecycle.TestUtils.OrderedTuples.START;
+import static androidx.lifecycle.TestUtils.OrderedTuples.STOP;
+import static androidx.lifecycle.TestUtils.flatMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import android.app.Activity;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+
+import androidx.core.util.Pair;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.testapp.CollectingLifecycleOwner;
+import androidx.lifecycle.testapp.CollectingSupportActivity;
+import androidx.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
+import androidx.lifecycle.testapp.TestEvent;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class ActivityFullLifecycleTest {
+    @Rule
+    public final ActivityTestRule<? extends CollectingLifecycleOwner> activityTestRule;
+
+    @Parameterized.Parameters
+    public static Class[] params() {
+        return new Class[]{CollectingSupportActivity.class,
+                FrameworkLifecycleRegistryActivity.class};
+    }
+
+    public ActivityFullLifecycleTest(Class<? extends Activity> activityClass) {
+        //noinspection unchecked
+        activityTestRule = new ActivityTestRule(activityClass);
+    }
+
+
+    @Test
+    public void testFullLifecycle() throws Throwable {
+        CollectingLifecycleOwner owner = activityTestRule.getActivity();
+        TestUtils.waitTillResumed(owner, activityTestRule);
+        activityTestRule.finishActivity();
+
+        TestUtils.waitTillDestroyed(owner, activityTestRule);
+        List<Pair<TestEvent, Event>> results = owner.copyCollectedEvents();
+        assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/FragmentInBackStackLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/FragmentInBackStackLifecycleTest.java
new file mode 100644
index 0000000..e20d95c
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/FragmentInBackStackLifecycleTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 androidx.lifecycle;
+
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.TestUtils.recreateActivity;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.iterableWithSize;
+
+import static java.util.Arrays.asList;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.testapp.EmptyActivity;
+import androidx.lifecycle.testapp.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentInBackStackLifecycleTest {
+    @Rule
+    public ActivityTestRule<EmptyActivity> activityTestRule = new ActivityTestRule<>(
+            EmptyActivity.class);
+
+    @Test
+    public void test() throws Throwable {
+        final ArrayList<Event> collectedEvents = new ArrayList<>();
+        LifecycleObserver collectingObserver = new LifecycleObserver() {
+            @OnLifecycleEvent(Event.ON_ANY)
+            void onAny(@SuppressWarnings("unused") LifecycleOwner owner, Event event) {
+                collectedEvents.add(event);
+            }
+        };
+        final FragmentActivity activity = activityTestRule.getActivity();
+        activityTestRule.runOnUiThread(() -> {
+            FragmentManager fm = activity.getSupportFragmentManager();
+            Fragment fragment = new Fragment();
+            fm.beginTransaction().add(R.id.fragment_container, fragment, "tag").addToBackStack(null)
+                    .commit();
+            fm.executePendingTransactions();
+
+            fragment.getLifecycle().addObserver(collectingObserver);
+            Fragment fragment2 = new Fragment();
+            fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
+                    .commit();
+            fm.executePendingTransactions();
+            assertThat(collectedEvents, is(asList(ON_CREATE, ON_START, ON_RESUME,
+                    ON_PAUSE, ON_STOP)));
+            collectedEvents.clear();
+        });
+        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();
+            Fragment fragment = fm.findFragmentByTag("tag");
+            fragment.getLifecycle().addObserver(collectingObserver);
+            assertThat(collectedEvents, iterableWithSize(0));
+            fm.popBackStackImmediate();
+            assertThat(collectedEvents, is(asList(ON_CREATE, ON_START, ON_RESUME)));
+        });
+    }
+
+
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/LiveDataOnSaveInstanceStateTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/LiveDataOnSaveInstanceStateTest.java
new file mode 100644
index 0000000..32312c0
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/LiveDataOnSaveInstanceStateTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.testapp.CollectingSupportActivity;
+import androidx.lifecycle.testapp.CollectingSupportFragment;
+import androidx.lifecycle.testapp.NavigationDialogActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LiveDataOnSaveInstanceStateTest {
+    @Rule
+    public ActivityTestRule<CollectingSupportActivity> mActivityTestRule =
+            new ActivityTestRule<>(CollectingSupportActivity.class);
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+    public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+        liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+    public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+        liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
+    public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> {
+            activity.replaceFragment(fragment);
+            fragment.replaceFragment(fragment2);
+        });
+
+        liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+
+        liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
+
+        liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
+        CollectingSupportActivity activity = mActivityTestRule.getActivity();
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        CollectingSupportFragment fragment2 = new CollectingSupportFragment();
+        mActivityTestRule.runOnUiThread(() -> {
+            activity.replaceFragment(fragment);
+            fragment.replaceFragment(fragment2);
+        });
+
+        liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
+    }
+
+    private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
+            throws Throwable {
+        final AtomicInteger atomicInteger = new AtomicInteger(0);
+        MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+        mActivityTestRule.runOnUiThread(() ->
+                mutableLiveData.observe(lifecycleOwner, atomicInteger::set));
+
+        final FragmentActivity dialogActivity = launchDialog();
+
+        TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule);
+
+        // Change the LiveData value and assert that the observer is not called given that the
+        // lifecycle is in the CREATED state.
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+        assertThat(atomicInteger.get(), is(0));
+
+        // Finish the dialog Activity, wait for the main activity to be resumed, and assert that
+        // the observer's onChanged method is called.
+        mActivityTestRule.runOnUiThread(dialogActivity::finish);
+        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+        assertThat(atomicInteger.get(), is(1));
+    }
+
+    private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
+            throws Throwable {
+        final AtomicInteger atomicInteger = new AtomicInteger(0);
+        MutableLiveData<Integer> mutableLiveData = new MutableLiveData<>();
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
+
+        TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
+
+        mActivityTestRule.runOnUiThread(() ->
+                mutableLiveData.observe(lifecycleOwner, atomicInteger::set));
+
+        // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
+        // lifecycleOwner to hit onPause (or enter the STARTED state).  On API 24 and above, this
+        // onPause should be the last lifecycle method called (and the STARTED state should be the
+        // final resting state).
+        launchDialog();
+        TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
+
+        // Change the LiveData's value and verify that the observer's onChanged method is called
+        // since we are in the STARTED state.
+        mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
+        assertThat(atomicInteger.get(), is(1));
+    }
+
+    private FragmentActivity launchDialog() throws Throwable {
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationDialogActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        FragmentActivity activity = mActivityTestRule.getActivity();
+        // helps with less flaky API 16 tests
+        Intent intent = new Intent(activity, NavigationDialogActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        activity.startActivity(intent);
+        FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+        TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule);
+        return fragmentActivity;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
new file mode 100644
index 0000000..7a6d389
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/PartiallyCoveredActivityTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.TestUtils.OrderedTuples.CREATE;
+import static androidx.lifecycle.TestUtils.OrderedTuples.DESTROY;
+import static androidx.lifecycle.TestUtils.OrderedTuples.PAUSE;
+import static androidx.lifecycle.TestUtils.OrderedTuples.RESUME;
+import static androidx.lifecycle.TestUtils.OrderedTuples.START;
+import static androidx.lifecycle.TestUtils.OrderedTuples.STOP;
+import static androidx.lifecycle.TestUtils.flatMap;
+import static androidx.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static androidx.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+
+import androidx.core.util.Pair;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.testapp.CollectingLifecycleOwner;
+import androidx.lifecycle.testapp.CollectingSupportActivity;
+import androidx.lifecycle.testapp.CollectingSupportFragment;
+import androidx.lifecycle.testapp.NavigationDialogActivity;
+import androidx.lifecycle.testapp.TestEvent;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Runs tests about the state when an activity is partially covered by another activity. Pre
+ * API 24, framework behavior changes so the test rely on whether state is saved or not and makes
+ * assertions accordingly.
+ */
+@SuppressWarnings("unchecked")
+@RunWith(Parameterized.class)
+@LargeTest
+public class PartiallyCoveredActivityTest {
+    private static final List[] IF_SAVED = new List[]{
+            // when overlaid
+            flatMap(CREATE, START, RESUME, PAUSE,
+                    singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))),
+            // post dialog dismiss
+            asList(new Pair<>(OWNER_CALLBACK, ON_RESUME),
+                    new Pair<>(LIFECYCLE_EVENT, ON_START),
+                    new Pair<>(LIFECYCLE_EVENT, ON_RESUME)),
+            // post finish
+            flatMap(PAUSE, STOP, DESTROY)};
+
+    private static final List[] IF_NOT_SAVED = new List[]{
+            // when overlaid
+            flatMap(CREATE, START, RESUME, PAUSE),
+            // post dialog dismiss
+            flatMap(RESUME),
+            // post finish
+            flatMap(PAUSE, STOP, DESTROY)};
+
+    private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;
+    private static final List<Pair<TestEvent, Lifecycle.Event>>[] EXPECTED =
+            sShouldSave ? IF_SAVED : IF_NOT_SAVED;
+
+    @Rule
+    public ActivityTestRule<CollectingSupportActivity> activityRule =
+            new ActivityTestRule<CollectingSupportActivity>(
+                    CollectingSupportActivity.class) {
+                @Override
+                protected Intent getActivityIntent() {
+                    // helps with less flaky API 16 tests
+                    Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
+                            CollectingSupportActivity.class);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+                    return intent;
+                }
+            };
+    private final boolean mDismissDialog;
+
+    @Parameterized.Parameters(name = "dismissDialog_{0}")
+    public static List<Boolean> dismissDialog() {
+        return asList(true, false);
+    }
+
+    public PartiallyCoveredActivityTest(boolean dismissDialog) {
+        mDismissDialog = dismissDialog;
+    }
+
+    @Test
+    public void coveredWithDialog_activity() throws Throwable {
+        final CollectingSupportActivity activity = activityRule.getActivity();
+        runTest(activity);
+    }
+
+    @Test
+    public void coveredWithDialog_fragment() throws Throwable {
+        CollectingSupportFragment fragment = new CollectingSupportFragment();
+        activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment));
+        runTest(fragment);
+    }
+
+    @Test
+    public void coveredWithDialog_childFragment() throws Throwable {
+        CollectingSupportFragment parentFragment = new CollectingSupportFragment();
+        CollectingSupportFragment childFragment = new CollectingSupportFragment();
+        activityRule.runOnUiThread(() -> {
+            activityRule.getActivity().replaceFragment(parentFragment);
+            parentFragment.replaceFragment(childFragment);
+        });
+        runTest(childFragment);
+    }
+
+    private void runTest(CollectingLifecycleOwner owner) throws Throwable {
+        TestUtils.waitTillResumed(owner, activityRule);
+        FragmentActivity dialog = launchDialog();
+        assertStateSaving();
+        waitForIdle();
+        assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
+        List<Pair<TestEvent, Lifecycle.Event>> expected;
+        if (mDismissDialog) {
+            dialog.finish();
+            TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
+            assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
+            expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
+        } else {
+            expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
+        }
+        CollectingSupportActivity activity = activityRule.getActivity();
+        activityRule.finishActivity();
+        TestUtils.waitTillDestroyed(activity, activityRule);
+        assertThat(owner.copyCollectedEvents(), is(expected));
+    }
+
+    // test sanity
+    private void assertStateSaving() throws ExecutionException, InterruptedException {
+        final CollectingSupportActivity activity = activityRule.getActivity();
+        if (sShouldSave) {
+            // state should be saved. wait for it to be saved
+            assertThat("test sanity",
+                    activity.waitForStateSave(20), is(true));
+            assertThat("test sanity", activity.getSupportFragmentManager()
+                    .isStateSaved(), is(true));
+        } else {
+            // should should not be saved
+            assertThat("test sanity", activity.getSupportFragmentManager()
+                    .isStateSaved(), is(false));
+        }
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private FragmentActivity launchDialog() throws Throwable {
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationDialogActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        FragmentActivity activity = activityRule.getActivity();
+
+        Intent intent = new Intent(activity, NavigationDialogActivity.class);
+        // disabling animations helps with less flaky API 16 tests
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        activity.startActivity(intent);
+        FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
+        TestUtils.waitTillResumed(fragmentActivity, activityRule);
+        return fragmentActivity;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/ProcessOwnerTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/ProcessOwnerTest.java
new file mode 100644
index 0000000..1e27357
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/ProcessOwnerTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.TestUtils.waitTillResumed;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.testapp.NavigationDialogActivity;
+import androidx.lifecycle.testapp.NavigationTestActivityFirst;
+import androidx.lifecycle.testapp.NavigationTestActivitySecond;
+import androidx.lifecycle.testapp.NonSupportActivity;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProcessOwnerTest {
+
+    @Rule
+    public ActivityTestRule<NavigationTestActivityFirst> activityTestRule =
+            new ActivityTestRule<>(NavigationTestActivityFirst.class);
+
+    static class ProcessObserver implements LifecycleObserver {
+        volatile boolean mChangedState;
+
+        @OnLifecycleEvent(Event.ON_ANY)
+        void onEvent() {
+            mChangedState = true;
+        }
+    }
+
+    private ProcessObserver mObserver = new ProcessObserver();
+
+    @After
+    public void tearDown() {
+        try {
+            // reassure that our observer is removed.
+            removeProcessObserver(mObserver);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testNavigation() throws Throwable {
+        FragmentActivity firstActivity = setupObserverOnResume();
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationTestActivitySecond.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        Intent intent = new Intent(firstActivity, NavigationTestActivitySecond.class);
+        firstActivity.finish();
+        firstActivity.startActivity(intent);
+
+        FragmentActivity secondActivity = (FragmentActivity) monitor.waitForActivity();
+        assertThat("Failed to navigate", secondActivity, notNullValue());
+        checkProcessObserverSilent(secondActivity);
+    }
+
+    @Test
+    public void testNavigationToNonSupport() throws Throwable {
+        FragmentActivity firstActivity = setupObserverOnResume();
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NonSupportActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        Intent intent = new Intent(firstActivity, NonSupportActivity.class);
+        firstActivity.finish();
+        firstActivity.startActivity(intent);
+        NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity();
+        assertThat("Failed to navigate", secondActivity, notNullValue());
+        checkProcessObserverSilent(secondActivity);
+    }
+
+    @Test
+    public void testRecreation() throws Throwable {
+        FragmentActivity activity = setupObserverOnResume();
+        FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
+        assertThat("Failed to recreate", recreated, notNullValue());
+        checkProcessObserverSilent(recreated);
+    }
+
+    @Test
+    public void testPressHomeButton() throws Throwable {
+        setupObserverOnResume();
+
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                NavigationDialogActivity.class.getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+
+        NavigationTestActivityFirst activity = activityTestRule.getActivity();
+        activity.startActivity(new Intent(activity, NavigationDialogActivity.class));
+        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(@SuppressWarnings("unused") LifecycleOwner provider,
+                    Event event) {
+                events.add(event);
+            }
+        };
+        addProcessObserver(collectingObserver);
+        events.clear();
+        assertThat(activity.moveTaskToBack(true), is(true));
+        Thread.sleep(ProcessLifecycleOwner.TIMEOUT_MS * 2);
+        assertThat(events.toArray(), is(new Event[]{ON_PAUSE, ON_STOP}));
+        events.clear();
+        Context context = InstrumentationRegistry.getContext();
+        context.startActivity(new Intent(activity, NavigationDialogActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        waitTillResumed(dialogActivity, activityTestRule);
+        assertThat(events.toArray(), is(new Event[]{ON_START, ON_RESUME}));
+        removeProcessObserver(collectingObserver);
+        dialogActivity.finish();
+    }
+
+    private FragmentActivity setupObserverOnResume() throws Throwable {
+        FragmentActivity firstActivity = activityTestRule.getActivity();
+        waitTillResumed(firstActivity, activityTestRule);
+        addProcessObserver(mObserver);
+        mObserver.mChangedState = false;
+        return firstActivity;
+    }
+
+    private void addProcessObserver(LifecycleObserver observer) throws Throwable {
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().addObserver(observer));
+    }
+
+    private void removeProcessObserver(LifecycleObserver observer) throws Throwable {
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().removeObserver(observer));
+    }
+
+    private void checkProcessObserverSilent(FragmentActivity activity) throws Throwable {
+        waitTillResumed(activity, activityTestRule);
+        assertThat(mObserver.mChangedState, is(false));
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
+    }
+
+    private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable {
+        assertThat(activity.awaitResumedState(), is(true));
+        assertThat(mObserver.mChangedState, is(false));
+        activityTestRule.runOnUiThread(() ->
+                ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SimpleAppFullLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SimpleAppFullLifecycleTest.java
new file mode 100644
index 0000000..3a79160
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SimpleAppFullLifecycleTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.State.CREATED;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.hamcrest.Matchers.notNullValue;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.Lifecycle.State;
+import androidx.lifecycle.testapp.SimpleAppLifecycleTestActivity;
+import androidx.lifecycle.testapp.SimpleAppLifecycleTestActivity.TestEventType;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class SimpleAppFullLifecycleTest {
+
+    @SuppressWarnings("unchecked")
+    private static final Pair[] EXPECTED_EVENTS_CONSTRUCTION =
+            new Pair[] {
+                new Pair(TestEventType.PROCESS_EVENT, Event.ON_CREATE),
+                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_CREATE),
+                new Pair(TestEventType.PROCESS_EVENT, Event.ON_START),
+                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_START),
+                new Pair(TestEventType.PROCESS_EVENT, Event.ON_RESUME),
+                new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_RESUME),
+            };
+
+    @SuppressWarnings("unchecked")
+    private static final Pair[] EXPECTED_EVENTS_DESTRUCTION =
+            new Pair[]{
+
+                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_PAUSE),
+                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_STOP),
+                    new Pair(TestEventType.ACTIVITY_EVENT, Event.ON_DESTROY),
+
+                    new Pair(TestEventType.PROCESS_EVENT, Event.ON_PAUSE),
+                    new Pair(TestEventType.PROCESS_EVENT, Event.ON_STOP),
+            };
+    @Rule
+    public ActivityTestRule<SimpleAppLifecycleTestActivity> activityTestRule =
+            new ActivityTestRule<>(SimpleAppLifecycleTestActivity.class, false, false);
+
+    @Before
+    public void setup() {
+        // cool down period, so application state will become DESTROYED
+        try {
+            Thread.sleep(ProcessLifecycleOwner.TIMEOUT_MS * 2);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        SimpleAppLifecycleTestActivity.startProcessObserver();
+    }
+
+    @After
+    public void tearDown() {
+        SimpleAppLifecycleTestActivity.stopProcessObserver();
+    }
+
+    @Test
+    public void testFullLifecycle() throws InterruptedException {
+        State currentState = ProcessLifecycleOwner.get().getLifecycle().getCurrentState();
+        assertThat(currentState, is(CREATED));
+        activityTestRule.launchActivity(null);
+        List<Pair<TestEventType, Event>> events = SimpleAppLifecycleTestActivity.awaitForEvents();
+        assertThat("Failed to await for events", events, notNullValue());
+        //noinspection ConstantConditions
+        assertThat(events.subList(0, 6).toArray(), is(EXPECTED_EVENTS_CONSTRUCTION));
+
+        // TODO: bug 35122523
+        for (Pair<TestEventType, Event> event: events.subList(6, 11)) {
+            assertThat(event, isIn(EXPECTED_EVENTS_DESTRUCTION));
+        }
+    }
+
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SynchronousActivityLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SynchronousActivityLifecycleTest.java
new file mode 100644
index 0000000..7c06e62
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SynchronousActivityLifecycleTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Build;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.UiThreadTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.testapp.LifecycleTestActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+
+/**
+ * It tests that an event is dispatched immediately after a call of corresponding OnXXX method
+ * during an execution of performXXX
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SynchronousActivityLifecycleTest {
+
+    @Rule
+    public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
+
+    @Test
+    public void testOnCreateCall() throws Throwable {
+        testSynchronousCall(Event.ON_CREATE,
+                activity -> {
+                },
+                activity -> getInstrumentation().callActivityOnCreate(activity, null));
+    }
+
+    @Test
+    public void testOnStartCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_START,
+                activity -> getInstrumentation().callActivityOnCreate(activity, null),
+                SynchronousActivityLifecycleTest::performStart);
+    }
+
+    @Test
+    public void testOnResumeCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_RESUME,
+                activity -> {
+                    getInstrumentation().callActivityOnCreate(activity, null);
+                    performStart(activity);
+                },
+                SynchronousActivityLifecycleTest::performResume);
+    }
+
+    @Test
+    public void testOnStopCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_STOP,
+                activity -> {
+                    getInstrumentation().callActivityOnCreate(activity, null);
+                    performStart(activity);
+                },
+                SynchronousActivityLifecycleTest::performStop);
+    }
+
+    @Test
+    public void testOnDestroyCall() throws Throwable {
+        testSynchronousCall(Lifecycle.Event.ON_DESTROY,
+                activity -> getInstrumentation().callActivityOnCreate(activity, null),
+                activity -> getInstrumentation().callActivityOnDestroy(activity));
+    }
+
+    public void testSynchronousCall(Event event, ActivityCall preInit, ActivityCall call)
+            throws Throwable {
+        uiThreadTestRule.runOnUiThread(() -> {
+            Intent intent = new Intent();
+            ComponentName cn = new ComponentName(LifecycleTestActivity.class.getPackage().getName(),
+                    LifecycleTestActivity.class.getName());
+            intent.setComponent(cn);
+            Instrumentation instrumentation = getInstrumentation();
+            try {
+                Application app =
+                        (Application) instrumentation.getTargetContext().getApplicationContext();
+                LifecycleTestActivity testActivity =
+                        (LifecycleTestActivity) instrumentation.newActivity(
+                                LifecycleTestActivity.class, instrumentation.getTargetContext(),
+                                null, app, intent, new ActivityInfo(), "bla", null, null, null);
+                preInit.call(testActivity);
+                TestObserver testObserver = new TestObserver(testActivity, event);
+                testActivity.getLifecycle().addObserver(testObserver);
+                testObserver.unmute();
+                call.call(testActivity);
+
+                assertThat(testObserver.mEventReceived, is(true));
+            } catch (Exception e) {
+                throw new Error(e);
+            }
+        });
+    }
+
+    // Instrumentation.callOnActivityCreate calls performCreate on mActivity,
+    // but Instrumentation.callOnActivityStart calls onStart instead of performStart. ¯\_(ツ)_/¯
+    private static void performStart(Activity activity) {
+        try {
+            Method m = Activity.class.getDeclaredMethod("performStart");
+            m.setAccessible(true);
+            m.invoke(activity);
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+    private static void performResume(Activity activity) {
+        try {
+            Method m = Activity.class.getDeclaredMethod("performResume");
+            m.setAccessible(true);
+            m.invoke(activity);
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+
+    private static void performStop(Activity activity) {
+        try {
+            if (Build.VERSION.SDK_INT >= 24) {
+                Method m = Activity.class.getDeclaredMethod("performStop", boolean.class);
+                m.setAccessible(true);
+                m.invoke(activity, false);
+            } else {
+                Method m = Activity.class.getDeclaredMethod("performStop");
+                m.setAccessible(true);
+                m.invoke(activity);
+            }
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+    private static class TestObserver implements GenericLifecycleObserver {
+        private final LifecycleTestActivity mActivity;
+        private final Event mExpectedEvent;
+        boolean mEventReceived = false;
+        boolean mMuted = true;
+
+        private TestObserver(LifecycleTestActivity activity, Event expectedEvent) {
+            this.mActivity = activity;
+            this.mExpectedEvent = expectedEvent;
+        }
+
+        void unmute() {
+            mMuted = false;
+        }
+
+        @Override
+        public void onStateChanged(LifecycleOwner lifecycleOwner, Event event) {
+            if (mMuted) {
+                return;
+            }
+            assertThat(event, is(mExpectedEvent));
+            assertThat(mActivity.mLifecycleCallFinished, is(true));
+            mEventReceived = true;
+        }
+    }
+
+    private interface ActivityCall {
+        void call(Activity activity);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/TestUtils.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/TestUtils.java
new file mode 100644
index 0000000..2045181
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/TestUtils.java
@@ -0,0 +1,168 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.State.CREATED;
+import static androidx.lifecycle.Lifecycle.State.DESTROYED;
+import static androidx.lifecycle.Lifecycle.State.RESUMED;
+import static androidx.lifecycle.Lifecycle.State.STARTED;
+import static androidx.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+import static androidx.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+
+import androidx.core.util.Pair;
+import androidx.lifecycle.testapp.TestEvent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+class TestUtils {
+
+    private static final long TIMEOUT_MS = 2000;
+
+    @SuppressWarnings("unchecked")
+    static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
+            throws Throwable {
+        ActivityMonitor monitor = new ActivityMonitor(
+                activity.getClass().getCanonicalName(), null, false);
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.addMonitor(monitor);
+        rule.runOnUiThread(activity::recreate);
+        T result;
+
+        // this guarantee that we will reinstall monitor between notifications about onDestroy
+        // and onCreate
+        //noinspection SynchronizationOnLocalVariableOrMethodParameter
+        synchronized (monitor) {
+            do {
+                // the documetation says "Block until an Activity is created
+                // that matches this monitor." This statement is true, but there are some other
+                // true statements like: "Block until an Activity is destoyed" or
+                // "Block until an Activity is resumed"...
+
+                // this call will release synchronization monitor's monitor
+                result = (T) monitor.waitForActivityWithTimeout(TIMEOUT_MS);
+                if (result == null) {
+                    throw new RuntimeException("Timeout. Failed to recreate an activity");
+                }
+            } while (result == activity);
+        }
+        return result;
+    }
+
+    static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, CREATED);
+    }
+
+    static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, STARTED);
+    }
+
+    static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, RESUMED);
+    }
+
+    static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
+            throws Throwable {
+        waitTillState(owner, activityRule, DESTROYED);
+    }
+
+    static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
+            Lifecycle.State state)
+            throws Throwable {
+        final CountDownLatch latch = new CountDownLatch(1);
+        activityRule.runOnUiThread(() -> {
+            Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
+            if (currentState == state) {
+                latch.countDown();
+            } else {
+                owner.getLifecycle().addObserver(new LifecycleObserver() {
+                    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+                    public void onStateChanged(LifecycleOwner provider) {
+                        if (provider.getLifecycle().getCurrentState() == state) {
+                            latch.countDown();
+                            provider.getLifecycle().removeObserver(this);
+                        }
+                    }
+                });
+            }
+        });
+        boolean latchResult = latch.await(1, TimeUnit.MINUTES);
+        assertThat("expected " + state + " never happened. Current state:"
+                        + owner.getLifecycle().getCurrentState(), latchResult, is(true));
+
+        // wait for another loop to ensure all observers are called
+        activityRule.runOnUiThread(() -> {
+            // do nothing
+        });
+    }
+
+    @SafeVarargs
+    static <T> List<T> flatMap(List<T>... items) {
+        ArrayList<T> result = new ArrayList<>();
+        for (List<T> item : items) {
+            result.addAll(item);
+        }
+        return result;
+    }
+
+    /**
+     * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
+     * in the order they should arrive.
+     */
+    @SuppressWarnings("unchecked")
+    static class OrderedTuples {
+        static final List<Pair<TestEvent, Lifecycle.Event>> CREATE =
+                Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
+                        new Pair(LIFECYCLE_EVENT, ON_CREATE));
+        static final List<Pair<TestEvent, Lifecycle.Event>> START =
+                Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
+                        new Pair(LIFECYCLE_EVENT, ON_START));
+        static final List<Pair<TestEvent, Lifecycle.Event>> RESUME =
+                Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
+                        new Pair(LIFECYCLE_EVENT, ON_RESUME));
+        static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE =
+                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
+                        new Pair(OWNER_CALLBACK, ON_PAUSE));
+        static final List<Pair<TestEvent, Lifecycle.Event>> STOP =
+                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
+                        new Pair(OWNER_CALLBACK, ON_STOP));
+        static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY =
+                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
+                        new Pair(OWNER_CALLBACK, ON_DESTROY));
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml b/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
index 5e1d0a0..19166dd 100644
--- a/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/lifecycle/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -11,37 +11,37 @@
   ~ limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools" package="android.arch.lifecycle.testapp">
+    xmlns:tools="http://schemas.android.com/tools" package="androidx.lifecycle.testapp">
 
     <application android:allowBackup="true" android:label="Test App" android:supportsRtl="true"
         tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
 
-        <activity android:name="android.arch.lifecycle.testapp.LifecycleTestActivity">
+        <activity android:name="androidx.lifecycle.testapp.LifecycleTestActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity android:name="android.arch.lifecycle.testapp.CollectingSupportActivity">
+        <activity android:name="androidx.lifecycle.testapp.CollectingSupportActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity android:name="android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity">
+        <activity android:name="androidx.lifecycle.testapp.FrameworkLifecycleRegistryActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
         <activity
-            android:name="android.arch.lifecycle.testapp.SimpleAppLifecycleTestActivity">
+            android:name="androidx.lifecycle.testapp.SimpleAppLifecycleTestActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity android:name="android.arch.lifecycle.testapp.NavigationTestActivityFirst"
+        <activity android:name="androidx.lifecycle.testapp.NavigationTestActivityFirst"
             android:launchMode="singleTask">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -49,7 +49,7 @@
             </intent-filter>
         </activity>
         <activity
-            android:name="android.arch.lifecycle.testapp.EmptyActivity">
+            android:name="androidx.lifecycle.testapp.EmptyActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
deleted file mode 100644
index 4213cab..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.support.v4.util.Pair;
-
-import java.util.List;
-
-/**
- * For activities that collect their events.
- */
-public interface CollectingLifecycleOwner extends LifecycleOwner {
-    /**
-     * Return a copy of currently collected events
-     *
-     * @return The list of collected events.
-     * @throws InterruptedException
-     */
-    List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents();
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportActivity.java
deleted file mode 100644
index f38d422..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportActivity.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.util.Pair;
-import android.widget.FrameLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * LifecycleRegistryOwner that extends FragmentActivity.
- */
-public class CollectingSupportActivity extends FragmentActivity implements
-        CollectingLifecycleOwner {
-
-    private final List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
-    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
-    private CountDownLatch mSavedStateLatch = new CountDownLatch(1);
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        FrameLayout layout = new FrameLayout(this);
-        layout.setId(R.id.fragment_container);
-        setContentView(layout);
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE));
-        getLifecycle().addObserver(mTestObserver);
-    }
-
-    /**
-     * replaces the main content fragment w/ the given fragment.
-     */
-    public void replaceFragment(Fragment fragment) {
-        getSupportFragmentManager()
-                .beginTransaction()
-                .add(R.id.fragment_container, fragment)
-                .commitNow();
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START));
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME));
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY));
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP));
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE));
-        // helps with less flaky API 16 tests.
-        overridePendingTransition(0, 0);
-    }
-
-    @Override
-    public List<Pair<TestEvent, Event>> copyCollectedEvents() {
-        return new ArrayList<>(mCollectedEvents);
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        mSavedStateLatch.countDown();
-    }
-
-    /**
-     * Waits for onSaveInstanceState to be called.
-     */
-    public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds)
-            throws InterruptedException {
-        return mSavedStateLatch.await(seconds, TimeUnit.SECONDS);
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportFragment.java
deleted file mode 100644
index 9bbbe16..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/CollectingSupportFragment.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import android.annotation.SuppressLint;
-import android.arch.lifecycle.Lifecycle;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v4.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A support fragment that collects all of its events.
- */
-@SuppressLint("ValidFragment")
-public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner {
-    private final List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents =
-            new ArrayList<>();
-    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
-        getLifecycle().addObserver(mTestObserver);
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-        //noinspection ConstantConditions
-        FrameLayout layout = new FrameLayout(container.getContext());
-        layout.setId(R.id.child_fragment_container);
-        return layout;
-    }
-
-    /**
-     * Runs a replace fragment transaction with 'fragment' on this Fragment.
-     */
-    public void replaceFragment(Fragment fragment) {
-        getChildFragmentManager()
-                .beginTransaction()
-                .add(R.id.child_fragment_container, fragment)
-                .commitNow();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
-    }
-
-    @Override
-    public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
-        return new ArrayList<>(mCollectedEvents);
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/EmptyActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/EmptyActivity.java
deleted file mode 100644
index 68a82e1..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/EmptyActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * empty activity
- */
-public class EmptyActivity extends FragmentActivity {
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.empty_activity_layout);
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
deleted file mode 100644
index cdf577c..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import android.app.Activity;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.util.Pair;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * LifecycleRegistryOwner that extends framework activity.
- */
-@SuppressWarnings("deprecation")
-public class FrameworkLifecycleRegistryActivity extends Activity implements
-        LifecycleRegistryOwner, CollectingLifecycleOwner {
-    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
-
-    @NonNull
-    @Override
-    public LifecycleRegistry getLifecycle() {
-        return mLifecycleRegistry;
-    }
-
-    private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
-    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
-    private CountDownLatch mLatch = new CountDownLatch(1);
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
-        getLifecycle().addObserver(mTestObserver);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
-        mLatch.countDown();
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
-    }
-
-    @Override
-    public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
-        return new ArrayList<>(mCollectedEvents);
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleTestActivity.java
deleted file mode 100644
index cf07aee..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/LifecycleTestActivity.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Activity for testing events by themselves
- */
-public class LifecycleTestActivity extends FragmentActivity {
-
-    /**
-     * identifies that
-     */
-    public boolean mLifecycleCallFinished;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        mLifecycleCallFinished = false;
-        super.onCreate(savedInstanceState);
-        mLifecycleCallFinished = true;
-    }
-
-    @Override
-    protected void onStart() {
-        mLifecycleCallFinished = false;
-        super.onStart();
-        mLifecycleCallFinished = true;
-    }
-
-    @Override
-    protected void onResume() {
-        mLifecycleCallFinished = false;
-        super.onResume();
-        mLifecycleCallFinished = true;
-    }
-
-    @Override
-    protected void onPause() {
-        mLifecycleCallFinished = false;
-        super.onPause();
-        mLifecycleCallFinished = true;
-    }
-
-    @Override
-    protected void onStop() {
-        mLifecycleCallFinished = false;
-        super.onStop();
-        mLifecycleCallFinished = true;
-    }
-
-    @Override
-    protected void onDestroy() {
-        mLifecycleCallFinished = false;
-        super.onDestroy();
-        mLifecycleCallFinished = true;
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java
deleted file mode 100644
index 7d53528..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.support.v4.app.FragmentActivity;
-
-/**
- *  an activity with Dialog theme.
- */
-public class NavigationDialogActivity extends FragmentActivity {
-    @Override
-    protected void onPause() {
-        super.onPause();
-        // helps with less flaky API 16 tests
-        overridePendingTransition(0, 0);
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
deleted file mode 100644
index 69fd478..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Activity for ProcessOwnerTest
- */
-public class NavigationTestActivityFirst extends FragmentActivity {
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
deleted file mode 100644
index 0f9a4c9..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.support.v4.app.FragmentActivity;
-
-/**
- * Activity for ProcessOwnerTest
- */
-public class NavigationTestActivitySecond extends FragmentActivity {
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NonSupportActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NonSupportActivity.java
deleted file mode 100644
index 835d846..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/NonSupportActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it
- * should work anyway.
- */
-public class NonSupportActivity extends Activity {
-
-    private static final int TIMEOUT = 1; //secs
-    private final Lock mLock = new ReentrantLock();
-    private Condition mIsResumedCondition = mLock.newCondition();
-    private boolean mIsResumed = false;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mLock.lock();
-        try {
-            mIsResumed = true;
-            mIsResumedCondition.signalAll();
-        } finally {
-            mLock.unlock();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mLock.lock();
-        try {
-            mIsResumed = false;
-        } finally {
-            mLock.unlock();
-        }
-    }
-
-    /**
-     *  awaits resumed state
-     * @return
-     * @throws InterruptedException
-     */
-    public boolean awaitResumedState() throws InterruptedException {
-        mLock.lock();
-        try {
-            while (!mIsResumed) {
-                if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) {
-                    return false;
-                }
-            }
-            return true;
-        } finally {
-            mLock.unlock();
-        }
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
deleted file mode 100644
index 77bd99f..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.arch.lifecycle.Lifecycle;
-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;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity for SimpleAppFullLifecycleTest
- */
-public class SimpleAppLifecycleTestActivity extends FragmentActivity {
-
-    public enum TestEventType {
-        PROCESS_EVENT,
-        ACTIVITY_EVENT
-    }
-
-    private static final long TIMEOUT_SECS = 10; // secs
-
-    static class TestObserver implements LifecycleObserver {
-
-        private TestEventType mType;
-
-        TestObserver(TestEventType type) {
-            mType = type;
-        }
-
-        @SuppressWarnings("unused")
-        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
-        void onEvent(LifecycleOwner provider, Lifecycle.Event event) {
-            sCollectedEvents.add(new Pair<>(mType, event));
-            sLatch.countDown();
-        }
-    }
-
-    static List<Pair<TestEventType, Lifecycle.Event>> sCollectedEvents = new ArrayList<>();
-    static CountDownLatch sLatch = new CountDownLatch(11);
-
-    /**
-     * start process observer
-     */
-    public static void startProcessObserver() {
-        ProcessLifecycleOwner.get().getLifecycle().addObserver(sProcessObserver);
-    }
-
-    /**
-     * stop process observer
-     */
-    public static void stopProcessObserver() {
-        ProcessLifecycleOwner.get().getLifecycle().removeObserver(sProcessObserver);
-    }
-
-    private static TestObserver sProcessObserver = new TestObserver(TestEventType.PROCESS_EVENT);
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getLifecycle().addObserver(new TestObserver(TestEventType.ACTIVITY_EVENT));
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        finish();
-    }
-
-    /**
-     * returns collected events
-     */
-    public static List<Pair<TestEventType, Lifecycle.Event>> awaitForEvents()
-            throws InterruptedException {
-        boolean success = sLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS);
-        return success ? sCollectedEvents : null;
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java
deleted file mode 100644
index 788045a..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestEvent.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-public enum TestEvent {
-    OWNER_CALLBACK,
-    LIFECYCLE_EVENT,
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java
deleted file mode 100644
index 00b8e16..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/TestObserver.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.OnLifecycleEvent;
-import android.support.v4.util.Pair;
-
-import java.util.List;
-
-class TestObserver implements LifecycleObserver {
-    private final List<Pair<TestEvent, Event>> mCollectedEvents;
-
-    TestObserver(List<Pair<TestEvent, Event>> collectedEvents) {
-        mCollectedEvents = collectedEvents;
-    }
-
-    @OnLifecycleEvent(ON_CREATE)
-    public void create(LifecycleOwner pr) {
-        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_CREATE));
-    }
-
-    @OnLifecycleEvent(ON_START)
-    public void start(LifecycleOwner pr) {
-        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_START));
-    }
-
-    @OnLifecycleEvent(ON_RESUME)
-    public void resume(LifecycleOwner pr) {
-        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_RESUME));
-    }
-    @OnLifecycleEvent(ON_PAUSE)
-    public void pause(LifecycleOwner pr) {
-        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_PAUSE));
-    }
-
-    @OnLifecycleEvent(ON_STOP)
-    public void stop(LifecycleOwner pr) {
-        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_STOP));
-    }
-
-    @OnLifecycleEvent(ON_DESTROY)
-    public void destroy(LifecycleOwner pr) {
-        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_DESTROY));
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/UsualFragment.java b/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/UsualFragment.java
deleted file mode 100644
index fb6cae0..0000000
--- a/lifecycle/integration-tests/testapp/src/main/java/android/arch/lifecycle/testapp/UsualFragment.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.testapp;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Simple fragment which does nothing.
- */
-public class UsualFragment extends Fragment {
-
-    @Nullable
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        return new View(getContext());
-    }
-}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingLifecycleOwner.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingLifecycleOwner.java
new file mode 100644
index 0000000..95353cb
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingLifecycleOwner.java
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import androidx.core.util.Pair;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+
+import java.util.List;
+
+/**
+ * For activities that collect their events.
+ */
+public interface CollectingLifecycleOwner extends LifecycleOwner {
+    /**
+     * Return a copy of currently collected events
+     *
+     * @return The list of collected events.
+     * @throws InterruptedException
+     */
+    List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents();
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingSupportActivity.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingSupportActivity.java
new file mode 100644
index 0000000..65cf61f
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingSupportActivity.java
@@ -0,0 +1,114 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import static androidx.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+import androidx.core.util.Pair;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.Lifecycle.Event;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends FragmentActivity.
+ */
+public class CollectingSupportActivity extends FragmentActivity implements
+        CollectingLifecycleOwner {
+
+    private final List<Pair<TestEvent, Event>> mCollectedEvents = new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+    private CountDownLatch mSavedStateLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        FrameLayout layout = new FrameLayout(this);
+        layout.setId(R.id.fragment_container);
+        setContentView(layout);
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    /**
+     * replaces the main content fragment w/ the given fragment.
+     */
+    public void replaceFragment(Fragment fragment) {
+        getSupportFragmentManager()
+                .beginTransaction()
+                .add(R.id.fragment_container, fragment)
+                .commitNow();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE));
+        // helps with less flaky API 16 tests.
+        overridePendingTransition(0, 0);
+    }
+
+    @Override
+    public List<Pair<TestEvent, Event>> copyCollectedEvents() {
+        return new ArrayList<>(mCollectedEvents);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        mSavedStateLatch.countDown();
+    }
+
+    /**
+     * Waits for onSaveInstanceState to be called.
+     */
+    public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds)
+            throws InterruptedException {
+        return mSavedStateLatch.await(seconds, TimeUnit.SECONDS);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingSupportFragment.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingSupportFragment.java
new file mode 100644
index 0000000..5add2f4
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/CollectingSupportFragment.java
@@ -0,0 +1,105 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import static androidx.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Lifecycle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A support fragment that collects all of its events.
+ */
+@SuppressLint("ValidFragment")
+public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner {
+    private final List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents =
+            new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        //noinspection ConstantConditions
+        FrameLayout layout = new FrameLayout(container.getContext());
+        layout.setId(R.id.child_fragment_container);
+        return layout;
+    }
+
+    /**
+     * Runs a replace fragment transaction with 'fragment' on this Fragment.
+     */
+    public void replaceFragment(Fragment fragment) {
+        getChildFragmentManager()
+                .beginTransaction()
+                .add(R.id.child_fragment_container, fragment)
+                .commitNow();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+    }
+
+    @Override
+    public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+        return new ArrayList<>(mCollectedEvents);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/EmptyActivity.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/EmptyActivity.java
new file mode 100644
index 0000000..2cb4c96
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/EmptyActivity.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 androidx.lifecycle.testapp;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * empty activity
+ */
+public class EmptyActivity extends FragmentActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.empty_activity_layout);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
new file mode 100644
index 0000000..d6ffbfb
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -0,0 +1,94 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import static androidx.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.LifecycleRegistryOwner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * LifecycleRegistryOwner that extends framework activity.
+ */
+@SuppressWarnings("deprecation")
+public class FrameworkLifecycleRegistryActivity extends Activity implements
+        LifecycleRegistryOwner, CollectingLifecycleOwner {
+    private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+
+    @NonNull
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mLifecycleRegistry;
+    }
+
+    private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
+    private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+    private CountDownLatch mLatch = new CountDownLatch(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+        getLifecycle().addObserver(mTestObserver);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+        mLatch.countDown();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+    }
+
+    @Override
+    public List<Pair<TestEvent, Lifecycle.Event>> copyCollectedEvents() {
+        return new ArrayList<>(mCollectedEvents);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/LifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/LifecycleTestActivity.java
new file mode 100644
index 0000000..f600c69
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/LifecycleTestActivity.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.testapp;
+
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * Activity for testing events by themselves
+ */
+public class LifecycleTestActivity extends FragmentActivity {
+
+    /**
+     * identifies that
+     */
+    public boolean mLifecycleCallFinished;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        mLifecycleCallFinished = false;
+        super.onCreate(savedInstanceState);
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onStart() {
+        mLifecycleCallFinished = false;
+        super.onStart();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onResume() {
+        mLifecycleCallFinished = false;
+        super.onResume();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onPause() {
+        mLifecycleCallFinished = false;
+        super.onPause();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onStop() {
+        mLifecycleCallFinished = false;
+        super.onStop();
+        mLifecycleCallFinished = true;
+    }
+
+    @Override
+    protected void onDestroy() {
+        mLifecycleCallFinished = false;
+        super.onDestroy();
+        mLifecycleCallFinished = true;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationDialogActivity.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationDialogActivity.java
new file mode 100644
index 0000000..81834b4
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationDialogActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ *  an activity with Dialog theme.
+ */
+public class NavigationDialogActivity extends FragmentActivity {
+    @Override
+    protected void onPause() {
+        super.onPause();
+        // helps with less flaky API 16 tests
+        overridePendingTransition(0, 0);
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationTestActivityFirst.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationTestActivityFirst.java
new file mode 100644
index 0000000..28c1e7f
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationTestActivityFirst.java
@@ -0,0 +1,25 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * Activity for ProcessOwnerTest
+ */
+public class NavigationTestActivityFirst extends FragmentActivity {
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationTestActivitySecond.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationTestActivitySecond.java
new file mode 100644
index 0000000..e78ec17
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NavigationTestActivitySecond.java
@@ -0,0 +1,25 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * Activity for ProcessOwnerTest
+ */
+public class NavigationTestActivitySecond extends FragmentActivity {
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NonSupportActivity.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NonSupportActivity.java
new file mode 100644
index 0000000..8b3ae72
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/NonSupportActivity.java
@@ -0,0 +1,86 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it
+ * should work anyway.
+ */
+public class NonSupportActivity extends Activity {
+
+    private static final int TIMEOUT = 1; //secs
+    private final Lock mLock = new ReentrantLock();
+    private Condition mIsResumedCondition = mLock.newCondition();
+    private boolean mIsResumed = false;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mLock.lock();
+        try {
+            mIsResumed = true;
+            mIsResumedCondition.signalAll();
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mLock.lock();
+        try {
+            mIsResumed = false;
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    /**
+     *  awaits resumed state
+     * @return
+     * @throws InterruptedException
+     */
+    public boolean awaitResumedState() throws InterruptedException {
+        mLock.lock();
+        try {
+            while (!mIsResumed) {
+                if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) {
+                    return false;
+                }
+            }
+            return true;
+        } finally {
+            mLock.unlock();
+        }
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/SimpleAppLifecycleTestActivity.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
new file mode 100644
index 0000000..c739568
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
@@ -0,0 +1,101 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.util.Pair;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.ProcessLifecycleOwner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity for SimpleAppFullLifecycleTest
+ */
+public class SimpleAppLifecycleTestActivity extends FragmentActivity {
+
+    public enum TestEventType {
+        PROCESS_EVENT,
+        ACTIVITY_EVENT
+    }
+
+    private static final long TIMEOUT_SECS = 10; // secs
+
+    static class TestObserver implements LifecycleObserver {
+
+        private TestEventType mType;
+
+        TestObserver(TestEventType type) {
+            mType = type;
+        }
+
+        @SuppressWarnings("unused")
+        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+        void onEvent(LifecycleOwner provider, Lifecycle.Event event) {
+            sCollectedEvents.add(new Pair<>(mType, event));
+            sLatch.countDown();
+        }
+    }
+
+    static List<Pair<TestEventType, Lifecycle.Event>> sCollectedEvents = new ArrayList<>();
+    static CountDownLatch sLatch = new CountDownLatch(11);
+
+    /**
+     * start process observer
+     */
+    public static void startProcessObserver() {
+        ProcessLifecycleOwner.get().getLifecycle().addObserver(sProcessObserver);
+    }
+
+    /**
+     * stop process observer
+     */
+    public static void stopProcessObserver() {
+        ProcessLifecycleOwner.get().getLifecycle().removeObserver(sProcessObserver);
+    }
+
+    private static TestObserver sProcessObserver = new TestObserver(TestEventType.PROCESS_EVENT);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getLifecycle().addObserver(new TestObserver(TestEventType.ACTIVITY_EVENT));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        finish();
+    }
+
+    /**
+     * returns collected events
+     */
+    public static List<Pair<TestEventType, Lifecycle.Event>> awaitForEvents()
+            throws InterruptedException {
+        boolean success = sLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS);
+        return success ? sCollectedEvents : null;
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/TestEvent.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/TestEvent.java
new file mode 100644
index 0000000..18ed17a
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/TestEvent.java
@@ -0,0 +1,22 @@
+/*
+ * 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 androidx.lifecycle.testapp;
+
+public enum TestEvent {
+    OWNER_CALLBACK,
+    LIFECYCLE_EVENT,
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/TestObserver.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/TestObserver.java
new file mode 100644
index 0000000..7a3b249
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/TestObserver.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 androidx.lifecycle.testapp;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
+
+import androidx.core.util.Pair;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
+
+import java.util.List;
+
+class TestObserver implements LifecycleObserver {
+    private final List<Pair<TestEvent, Event>> mCollectedEvents;
+
+    TestObserver(List<Pair<TestEvent, Event>> collectedEvents) {
+        mCollectedEvents = collectedEvents;
+    }
+
+    @OnLifecycleEvent(ON_CREATE)
+    public void create(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_CREATE));
+    }
+
+    @OnLifecycleEvent(ON_START)
+    public void start(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_START));
+    }
+
+    @OnLifecycleEvent(ON_RESUME)
+    public void resume(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_RESUME));
+    }
+    @OnLifecycleEvent(ON_PAUSE)
+    public void pause(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_PAUSE));
+    }
+
+    @OnLifecycleEvent(ON_STOP)
+    public void stop(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_STOP));
+    }
+
+    @OnLifecycleEvent(ON_DESTROY)
+    public void destroy(LifecycleOwner pr) {
+        mCollectedEvents.add(new Pair<>(LIFECYCLE_EVENT, ON_DESTROY));
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/UsualFragment.java b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/UsualFragment.java
new file mode 100644
index 0000000..242b3c2
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/main/java/androidx/lifecycle/testapp/UsualFragment.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+/**
+ * Simple fragment which does nothing.
+ */
+public class UsualFragment extends Fragment {
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return new View(getContext());
+    }
+}
diff --git a/lifecycle/integration-tests/testapp/src/main/res/layout/empty_activity_layout.xml b/lifecycle/integration-tests/testapp/src/main/res/layout/empty_activity_layout.xml
index d476848..d906eb8 100644
--- a/lifecycle/integration-tests/testapp/src/main/res/layout/empty_activity_layout.xml
+++ b/lifecycle/integration-tests/testapp/src/main/res/layout/empty_activity_layout.xml
@@ -21,7 +21,7 @@
     android:id="@+id/activity_main"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context="android.arch.lifecycle.activity.FragmentLifecycleActivity">
+    tools:context="androidx.lifecycle.activity.FragmentLifecycleActivity">
     <FrameLayout
         android:id="@+id/fragment_container"
         android:layout_width="match_parent"
diff --git a/lifecycle/integration-tests/testapp/src/test/java/android/arch/lifecycle/GeneratedAdaptersTest.java b/lifecycle/integration-tests/testapp/src/test/java/android/arch/lifecycle/GeneratedAdaptersTest.java
deleted file mode 100644
index 2abb511..0000000
--- a/lifecycle/integration-tests/testapp/src/test/java/android/arch/lifecycle/GeneratedAdaptersTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_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/lifecycle/integration-tests/testapp/src/test/java/androidx/lifecycle/GeneratedAdaptersTest.java b/lifecycle/integration-tests/testapp/src/test/java/androidx/lifecycle/GeneratedAdaptersTest.java
new file mode 100644
index 0000000..9ebb880
--- /dev/null
+++ b/lifecycle/integration-tests/testapp/src/test/java/androidx/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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_ANY;
+import static androidx.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/lifecycle/livedata-core/api/current.txt b/lifecycle/livedata-core/api/current.txt
index 7e1d451..5cd4a08 100644
--- a/lifecycle/livedata-core/api/current.txt
+++ b/lifecycle/livedata-core/api/current.txt
@@ -1,21 +1,21 @@
-package android.arch.lifecycle {
+package androidx.lifecycle {
 
   public abstract class LiveData<T> {
     ctor public LiveData();
     method public T getValue();
     method public boolean hasActiveObservers();
     method public boolean hasObservers();
-    method public void observe(android.arch.lifecycle.LifecycleOwner, android.arch.lifecycle.Observer<? super T>);
-    method public void observeForever(android.arch.lifecycle.Observer<? super T>);
+    method public void observe(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Observer<? super T>);
+    method public void observeForever(androidx.lifecycle.Observer<? super T>);
     method protected void onActive();
     method protected void onInactive();
     method protected void postValue(T);
-    method public void removeObserver(android.arch.lifecycle.Observer<? super T>);
-    method public void removeObservers(android.arch.lifecycle.LifecycleOwner);
+    method public void removeObserver(androidx.lifecycle.Observer<? super T>);
+    method public void removeObservers(androidx.lifecycle.LifecycleOwner);
     method protected void setValue(T);
   }
 
-  public class MutableLiveData<T> extends android.arch.lifecycle.LiveData {
+  public class MutableLiveData<T> extends androidx.lifecycle.LiveData {
     ctor public MutableLiveData();
     method public void postValue(T);
     method public void setValue(T);
diff --git a/lifecycle/livedata-core/api/1.1.0.txt b/lifecycle/livedata-core/api_legacy/1.1.0.txt
similarity index 100%
rename from lifecycle/livedata-core/api/1.1.0.txt
rename to lifecycle/livedata-core/api_legacy/1.1.0.txt
diff --git a/lifecycle/livedata-core/api_legacy/current.txt b/lifecycle/livedata-core/api_legacy/current.txt
new file mode 100644
index 0000000..7e1d451
--- /dev/null
+++ b/lifecycle/livedata-core/api_legacy/current.txt
@@ -0,0 +1,29 @@
+package android.arch.lifecycle {
+
+  public abstract class LiveData<T> {
+    ctor public LiveData();
+    method public T getValue();
+    method public boolean hasActiveObservers();
+    method public boolean hasObservers();
+    method public void observe(android.arch.lifecycle.LifecycleOwner, android.arch.lifecycle.Observer<? super T>);
+    method public void observeForever(android.arch.lifecycle.Observer<? super T>);
+    method protected void onActive();
+    method protected void onInactive();
+    method protected void postValue(T);
+    method public void removeObserver(android.arch.lifecycle.Observer<? super T>);
+    method public void removeObservers(android.arch.lifecycle.LifecycleOwner);
+    method protected void setValue(T);
+  }
+
+  public class MutableLiveData<T> extends android.arch.lifecycle.LiveData {
+    ctor public MutableLiveData();
+    method public void postValue(T);
+    method public void setValue(T);
+  }
+
+  public abstract interface Observer<T> {
+    method public abstract void onChanged(T);
+  }
+
+}
+
diff --git a/lifecycle/livedata-core/build.gradle b/lifecycle/livedata-core/build.gradle
index becb17e..76f6c83 100644
--- a/lifecycle/livedata-core/build.gradle
+++ b/lifecycle/livedata-core/build.gradle
@@ -24,11 +24,11 @@
 }
 
 dependencies {
-    implementation(project(":arch:common"))
-    implementation(project(":arch:runtime"))
-    api(project(":lifecycle:common"))
+    implementation(project(":arch:core-common"))
+    implementation(project(":arch:core-runtime"))
+    api(project(":lifecycle:lifecycle-common"))
 
-    testImplementation(project(":lifecycle:runtime"))
+    testImplementation(project(":lifecycle:lifecycle-runtime"))
     testImplementation(project(":arch:core-testing"))
     testImplementation(JUNIT)
     testImplementation(MOCKITO_CORE)
diff --git a/lifecycle/livedata-core/src/main/AndroidManifest.xml b/lifecycle/livedata-core/src/main/AndroidManifest.xml
index 6d2f964..43abea7 100644
--- a/lifecycle/livedata-core/src/main/AndroidManifest.xml
+++ b/lifecycle/livedata-core/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle.livedata.core">
+          package="androidx.lifecycle.livedata.core">
 </manifest>
diff --git a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java
deleted file mode 100644
index b49185a..0000000
--- a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/LiveData.java
+++ /dev/null
@@ -1,441 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * LiveData is a data holder class that can be observed within a given lifecycle.
- * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
- * this observer will be notified about modifications of the wrapped data only if the paired
- * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
- * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
- * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
- * about modifications. For those observers, you should manually call
- * {@link #removeObserver(Observer)}.
- *
- * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
- * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
- * activities and fragments where they can safely observe LiveData and not worry about leaks:
- * they will be instantly unsubscribed when they are destroyed.
- *
- * <p>
- * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
- * to get notified when number of active {@link Observer}s change between 0 and 1.
- * This allows LiveData to release any heavy resources when it does not have any Observers that
- * are actively observing.
- * <p>
- * This class is designed to hold individual data fields of {@link ViewModel},
- * but can also be used for sharing data between different modules in your application
- * in a decoupled fashion.
- *
- * @param <T> The type of data held by this instance
- * @see ViewModel
- */
-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 SafeIterableMap<Observer<? super T>, ObserverWrapper> 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(ObserverWrapper observer) {
-        if (!observer.mActive) {
-            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 (!observer.shouldBeActive()) {
-            observer.activeStateChanged(false);
-            return;
-        }
-        if (observer.mLastVersion >= mVersion) {
-            return;
-        }
-        observer.mLastVersion = mVersion;
-        //noinspection unchecked
-        observer.mObserver.onChanged((T) mData);
-    }
-
-    private void dispatchingValue(@Nullable ObserverWrapper initiator) {
-        if (mDispatchingValue) {
-            mDispatchInvalidated = true;
-            return;
-        }
-        mDispatchingValue = true;
-        do {
-            mDispatchInvalidated = false;
-            if (initiator != null) {
-                considerNotify(initiator);
-                initiator = null;
-            } else {
-                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
-                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
-                    considerNotify(iterator.next().getValue());
-                    if (mDispatchInvalidated) {
-                        break;
-                    }
-                }
-            }
-        } while (mDispatchInvalidated);
-        mDispatchingValue = false;
-    }
-
-    /**
-     * Adds the given observer to the observers list within the lifespan of the given
-     * owner. The events are dispatched on the main thread. If LiveData already has data
-     * set, it will be delivered to the observer.
-     * <p>
-     * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
-     * or {@link Lifecycle.State#RESUMED} state (active).
-     * <p>
-     * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
-     * automatically be removed.
-     * <p>
-     * When data changes while the {@code owner} is not active, it will not receive any updates.
-     * If it becomes active again, it will receive the last available data automatically.
-     * <p>
-     * LiveData keeps a strong reference to the observer and the owner as long as the
-     * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
-     * the observer &amp; the owner.
-     * <p>
-     * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
-     * ignores the call.
-     * <p>
-     * If the given owner, observer tuple is already in the list, the call is ignored.
-     * If the observer is already in the list with another owner, LiveData throws an
-     * {@link IllegalArgumentException}.
-     *
-     * @param owner    The LifecycleOwner which controls the observer
-     * @param observer The observer that will receive the events
-     */
-    @MainThread
-    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
-        assertMainThread("observe");
-        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
-            // ignore
-            return;
-        }
-        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
-        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
-        if (existing != null && !existing.isAttachedTo(owner)) {
-            throw new IllegalArgumentException("Cannot add the same observer"
-                    + " with different lifecycles");
-        }
-        if (existing != null) {
-            return;
-        }
-        owner.getLifecycle().addObserver(wrapper);
-    }
-
-    /**
-     * Adds the given observer to the observers list. This call is similar to
-     * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
-     * is always active. This means that the given observer will receive all events and will never
-     * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
-     * observing this LiveData.
-     * While LiveData has one of such observers, it will be considered
-     * as active.
-     * <p>
-     * If the observer was already added with an owner to this LiveData, LiveData throws an
-     * {@link IllegalArgumentException}.
-     *
-     * @param observer The observer that will receive the events
-     */
-    @MainThread
-    public void observeForever(@NonNull Observer<? super T> observer) {
-        assertMainThread("observeForever");
-        AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
-        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
-        if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
-            throw new IllegalArgumentException("Cannot add the same observer"
-                    + " with different lifecycles");
-        }
-        if (existing != null) {
-            return;
-        }
-        wrapper.activeStateChanged(true);
-    }
-
-    /**
-     * Removes the given observer from the observers list.
-     *
-     * @param observer The Observer to receive events.
-     */
-    @MainThread
-    public void removeObserver(@NonNull final Observer<? super T> observer) {
-        assertMainThread("removeObserver");
-        ObserverWrapper removed = mObservers.remove(observer);
-        if (removed == null) {
-            return;
-        }
-        removed.detachObserver();
-        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.
-     */
-    @SuppressWarnings("WeakerAccess")
-    @MainThread
-    public void removeObservers(@NonNull final LifecycleOwner owner) {
-        assertMainThread("removeObservers");
-        for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) {
-            if (entry.getValue().isAttachedTo(owner)) {
-                removeObserver(entry.getKey());
-            }
-        }
-    }
-
-    /**
-     * Posts a task to a main thread to set the given value. So if you have a following code
-     * executed in the main thread:
-     * <pre class="prettyprint">
-     * liveData.postValue("a");
-     * liveData.setValue("b");
-     * </pre>
-     * The value "b" would be set at first and later the main thread would override it with
-     * the value "a".
-     * <p>
-     * If you called this method multiple times before a main thread executed a posted task, only
-     * the last value would be dispatched.
-     *
-     * @param value The new value
-     */
-    protected void postValue(T value) {
-        boolean postTask;
-        synchronized (mDataLock) {
-            postTask = mPendingData == NOT_SET;
-            mPendingData = value;
-        }
-        if (!postTask) {
-            return;
-        }
-        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
-    }
-
-    /**
-     * Sets the value. If there are active observers, the value will be dispatched to them.
-     * <p>
-     * This method must be called from the main thread. If you need set a value from a background
-     * thread, you can use {@link #postValue(Object)}
-     *
-     * @param value The new value
-     */
-    @MainThread
-    protected void setValue(T value) {
-        assertMainThread("setValue");
-        mVersion++;
-        mData = value;
-        dispatchingValue(null);
-    }
-
-    /**
-     * Returns the current value.
-     * Note that calling this method on a background thread does not guarantee that the latest
-     * value set will be received.
-     *
-     * @return the current value
-     */
-    @Nullable
-    public T getValue() {
-        Object data = mData;
-        if (data != NOT_SET) {
-            //noinspection unchecked
-            return (T) data;
-        }
-        return null;
-    }
-
-    int getVersion() {
-        return mVersion;
-    }
-
-    /**
-     * Called when the number of active observers change to 1 from 0.
-     * <p>
-     * This callback can be used to know that this LiveData is being used thus should be kept
-     * up to date.
-     */
-    protected void onActive() {
-
-    }
-
-    /**
-     * Called when the number of active observers change from 1 to 0.
-     * <p>
-     * This does not mean that there are no observers left, there may still be observers but their
-     * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
-     * (like an Activity in the back stack).
-     * <p>
-     * You can check if there are observers via {@link #hasObservers()}.
-     */
-    protected void onInactive() {
-
-    }
-
-    /**
-     * Returns true if this LiveData has observers.
-     *
-     * @return true if this LiveData has observers
-     */
-    @SuppressWarnings("WeakerAccess")
-    public boolean hasObservers() {
-        return mObservers.size() > 0;
-    }
-
-    /**
-     * Returns true if this LiveData has active observers.
-     *
-     * @return true if this LiveData has active observers
-     */
-    @SuppressWarnings("WeakerAccess")
-    public boolean hasActiveObservers() {
-        return mActiveCount > 0;
-    }
-
-    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
-        @NonNull final LifecycleOwner mOwner;
-
-        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
-            super(observer);
-            mOwner = owner;
-        }
-
-        @Override
-        boolean shouldBeActive() {
-            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
-        }
-
-        @Override
-        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
-            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
-                removeObserver(mObserver);
-                return;
-            }
-            activeStateChanged(shouldBeActive());
-        }
-
-        @Override
-        boolean isAttachedTo(LifecycleOwner owner) {
-            return mOwner == owner;
-        }
-
-        @Override
-        void detachObserver() {
-            mOwner.getLifecycle().removeObserver(this);
-        }
-    }
-
-    private abstract class ObserverWrapper {
-        final Observer<? super T> mObserver;
-        boolean mActive;
-        int mLastVersion = START_VERSION;
-
-        ObserverWrapper(Observer<? super T> observer) {
-            mObserver = observer;
-        }
-
-        abstract boolean shouldBeActive();
-
-        boolean isAttachedTo(LifecycleOwner owner) {
-            return false;
-        }
-
-        void detachObserver() {
-        }
-
-        void activeStateChanged(boolean newActive) {
-            if (newActive == mActive) {
-                return;
-            }
-            // immediately set active state, so we'd never dispatch anything to inactive
-            // owner
-            mActive = newActive;
-            boolean wasInactive = LiveData.this.mActiveCount == 0;
-            LiveData.this.mActiveCount += mActive ? 1 : -1;
-            if (wasInactive && mActive) {
-                onActive();
-            }
-            if (LiveData.this.mActiveCount == 0 && !mActive) {
-                onInactive();
-            }
-            if (mActive) {
-                dispatchingValue(this);
-            }
-        }
-    }
-
-    private class AlwaysActiveObserver extends ObserverWrapper {
-
-        AlwaysActiveObserver(Observer<? super T> observer) {
-            super(observer);
-        }
-
-        @Override
-        boolean shouldBeActive() {
-            return true;
-        }
-    }
-
-    private static void assertMainThread(String methodName) {
-        if (!ArchTaskExecutor.getInstance().isMainThread()) {
-            throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
-                    + " thread");
-        }
-    }
-}
diff --git a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/MutableLiveData.java b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/MutableLiveData.java
deleted file mode 100644
index ecd7752..0000000
--- a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/MutableLiveData.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-/**
- * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
- *
- * @param <T> The type of data hold by this instance
- */
-@SuppressWarnings("WeakerAccess")
-public class MutableLiveData<T> extends LiveData<T> {
-    @Override
-    public void postValue(T value) {
-        super.postValue(value);
-    }
-
-    @Override
-    public void setValue(T value) {
-        super.setValue(value);
-    }
-}
diff --git a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/Observer.java b/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/Observer.java
deleted file mode 100644
index 0e36775..0000000
--- a/lifecycle/livedata-core/src/main/java/android/arch/lifecycle/Observer.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.annotation.Nullable;
-
-/**
- * A simple callback that can receive from {@link LiveData}.
- *
- * @param <T> The type of the parameter
- *
- * @see LiveData LiveData - for a usage description.
- */
-public interface Observer<T> {
-    /**
-     * Called when the data is changed.
-     * @param t  The new data
-     */
-    void onChanged(@Nullable T t);
-}
diff --git a/lifecycle/livedata-core/src/main/java/androidx/lifecycle/LiveData.java b/lifecycle/livedata-core/src/main/java/androidx/lifecycle/LiveData.java
new file mode 100644
index 0000000..8c94a95
--- /dev/null
+++ b/lifecycle/livedata-core/src/main/java/androidx/lifecycle/LiveData.java
@@ -0,0 +1,441 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.State.DESTROYED;
+import static androidx.lifecycle.Lifecycle.State.STARTED;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.internal.SafeIterableMap;
+import androidx.arch.core.executor.ArchTaskExecutor;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * LiveData is a data holder class that can be observed within a given lifecycle.
+ * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
+ * this observer will be notified about modifications of the wrapped data only if the paired
+ * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
+ * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
+ * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
+ * about modifications. For those observers, you should manually call
+ * {@link #removeObserver(Observer)}.
+ *
+ * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
+ * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
+ * activities and fragments where they can safely observe LiveData and not worry about leaks:
+ * they will be instantly unsubscribed when they are destroyed.
+ *
+ * <p>
+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
+ * to get notified when number of active {@link Observer}s change between 0 and 1.
+ * This allows LiveData to release any heavy resources when it does not have any Observers that
+ * are actively observing.
+ * <p>
+ * This class is designed to hold individual data fields of {@link ViewModel},
+ * but can also be used for sharing data between different modules in your application
+ * in a decoupled fashion.
+ *
+ * @param <T> The type of data held by this instance
+ * @see ViewModel
+ */
+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 SafeIterableMap<Observer<? super T>, ObserverWrapper> 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(ObserverWrapper observer) {
+        if (!observer.mActive) {
+            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 (!observer.shouldBeActive()) {
+            observer.activeStateChanged(false);
+            return;
+        }
+        if (observer.mLastVersion >= mVersion) {
+            return;
+        }
+        observer.mLastVersion = mVersion;
+        //noinspection unchecked
+        observer.mObserver.onChanged((T) mData);
+    }
+
+    private void dispatchingValue(@Nullable ObserverWrapper initiator) {
+        if (mDispatchingValue) {
+            mDispatchInvalidated = true;
+            return;
+        }
+        mDispatchingValue = true;
+        do {
+            mDispatchInvalidated = false;
+            if (initiator != null) {
+                considerNotify(initiator);
+                initiator = null;
+            } else {
+                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
+                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
+                    considerNotify(iterator.next().getValue());
+                    if (mDispatchInvalidated) {
+                        break;
+                    }
+                }
+            }
+        } while (mDispatchInvalidated);
+        mDispatchingValue = false;
+    }
+
+    /**
+     * Adds the given observer to the observers list within the lifespan of the given
+     * owner. The events are dispatched on the main thread. If LiveData already has data
+     * set, it will be delivered to the observer.
+     * <p>
+     * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+     * or {@link Lifecycle.State#RESUMED} state (active).
+     * <p>
+     * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+     * automatically be removed.
+     * <p>
+     * When data changes while the {@code owner} is not active, it will not receive any updates.
+     * If it becomes active again, it will receive the last available data automatically.
+     * <p>
+     * LiveData keeps a strong reference to the observer and the owner as long as the
+     * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
+     * the observer &amp; the owner.
+     * <p>
+     * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+     * ignores the call.
+     * <p>
+     * If the given owner, observer tuple is already in the list, the call is ignored.
+     * If the observer is already in the list with another owner, LiveData throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param owner    The LifecycleOwner which controls the observer
+     * @param observer The observer that will receive the events
+     */
+    @MainThread
+    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
+        assertMainThread("observe");
+        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+            // ignore
+            return;
+        }
+        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
+        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
+        if (existing != null && !existing.isAttachedTo(owner)) {
+            throw new IllegalArgumentException("Cannot add the same observer"
+                    + " with different lifecycles");
+        }
+        if (existing != null) {
+            return;
+        }
+        owner.getLifecycle().addObserver(wrapper);
+    }
+
+    /**
+     * Adds the given observer to the observers list. This call is similar to
+     * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
+     * is always active. This means that the given observer will receive all events and will never
+     * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
+     * observing this LiveData.
+     * While LiveData has one of such observers, it will be considered
+     * as active.
+     * <p>
+     * If the observer was already added with an owner to this LiveData, LiveData throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param observer The observer that will receive the events
+     */
+    @MainThread
+    public void observeForever(@NonNull Observer<? super T> observer) {
+        assertMainThread("observeForever");
+        AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
+        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
+        if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
+            throw new IllegalArgumentException("Cannot add the same observer"
+                    + " with different lifecycles");
+        }
+        if (existing != null) {
+            return;
+        }
+        wrapper.activeStateChanged(true);
+    }
+
+    /**
+     * Removes the given observer from the observers list.
+     *
+     * @param observer The Observer to receive events.
+     */
+    @MainThread
+    public void removeObserver(@NonNull final Observer<? super T> observer) {
+        assertMainThread("removeObserver");
+        ObserverWrapper removed = mObservers.remove(observer);
+        if (removed == null) {
+            return;
+        }
+        removed.detachObserver();
+        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.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @MainThread
+    public void removeObservers(@NonNull final LifecycleOwner owner) {
+        assertMainThread("removeObservers");
+        for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) {
+            if (entry.getValue().isAttachedTo(owner)) {
+                removeObserver(entry.getKey());
+            }
+        }
+    }
+
+    /**
+     * Posts a task to a main thread to set the given value. So if you have a following code
+     * executed in the main thread:
+     * <pre class="prettyprint">
+     * liveData.postValue("a");
+     * liveData.setValue("b");
+     * </pre>
+     * The value "b" would be set at first and later the main thread would override it with
+     * the value "a".
+     * <p>
+     * If you called this method multiple times before a main thread executed a posted task, only
+     * the last value would be dispatched.
+     *
+     * @param value The new value
+     */
+    protected void postValue(T value) {
+        boolean postTask;
+        synchronized (mDataLock) {
+            postTask = mPendingData == NOT_SET;
+            mPendingData = value;
+        }
+        if (!postTask) {
+            return;
+        }
+        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
+    }
+
+    /**
+     * Sets the value. If there are active observers, the value will be dispatched to them.
+     * <p>
+     * This method must be called from the main thread. If you need set a value from a background
+     * thread, you can use {@link #postValue(Object)}
+     *
+     * @param value The new value
+     */
+    @MainThread
+    protected void setValue(T value) {
+        assertMainThread("setValue");
+        mVersion++;
+        mData = value;
+        dispatchingValue(null);
+    }
+
+    /**
+     * Returns the current value.
+     * Note that calling this method on a background thread does not guarantee that the latest
+     * value set will be received.
+     *
+     * @return the current value
+     */
+    @Nullable
+    public T getValue() {
+        Object data = mData;
+        if (data != NOT_SET) {
+            //noinspection unchecked
+            return (T) data;
+        }
+        return null;
+    }
+
+    int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Called when the number of active observers change to 1 from 0.
+     * <p>
+     * This callback can be used to know that this LiveData is being used thus should be kept
+     * up to date.
+     */
+    protected void onActive() {
+
+    }
+
+    /**
+     * Called when the number of active observers change from 1 to 0.
+     * <p>
+     * This does not mean that there are no observers left, there may still be observers but their
+     * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
+     * (like an Activity in the back stack).
+     * <p>
+     * You can check if there are observers via {@link #hasObservers()}.
+     */
+    protected void onInactive() {
+
+    }
+
+    /**
+     * Returns true if this LiveData has observers.
+     *
+     * @return true if this LiveData has observers
+     */
+    @SuppressWarnings("WeakerAccess")
+    public boolean hasObservers() {
+        return mObservers.size() > 0;
+    }
+
+    /**
+     * Returns true if this LiveData has active observers.
+     *
+     * @return true if this LiveData has active observers
+     */
+    @SuppressWarnings("WeakerAccess")
+    public boolean hasActiveObservers() {
+        return mActiveCount > 0;
+    }
+
+    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
+        @NonNull final LifecycleOwner mOwner;
+
+        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
+            super(observer);
+            mOwner = owner;
+        }
+
+        @Override
+        boolean shouldBeActive() {
+            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
+        }
+
+        @Override
+        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
+                removeObserver(mObserver);
+                return;
+            }
+            activeStateChanged(shouldBeActive());
+        }
+
+        @Override
+        boolean isAttachedTo(LifecycleOwner owner) {
+            return mOwner == owner;
+        }
+
+        @Override
+        void detachObserver() {
+            mOwner.getLifecycle().removeObserver(this);
+        }
+    }
+
+    private abstract class ObserverWrapper {
+        final Observer<? super T> mObserver;
+        boolean mActive;
+        int mLastVersion = START_VERSION;
+
+        ObserverWrapper(Observer<? super T> observer) {
+            mObserver = observer;
+        }
+
+        abstract boolean shouldBeActive();
+
+        boolean isAttachedTo(LifecycleOwner owner) {
+            return false;
+        }
+
+        void detachObserver() {
+        }
+
+        void activeStateChanged(boolean newActive) {
+            if (newActive == mActive) {
+                return;
+            }
+            // immediately set active state, so we'd never dispatch anything to inactive
+            // owner
+            mActive = newActive;
+            boolean wasInactive = LiveData.this.mActiveCount == 0;
+            LiveData.this.mActiveCount += mActive ? 1 : -1;
+            if (wasInactive && mActive) {
+                onActive();
+            }
+            if (LiveData.this.mActiveCount == 0 && !mActive) {
+                onInactive();
+            }
+            if (mActive) {
+                dispatchingValue(this);
+            }
+        }
+    }
+
+    private class AlwaysActiveObserver extends ObserverWrapper {
+
+        AlwaysActiveObserver(Observer<? super T> observer) {
+            super(observer);
+        }
+
+        @Override
+        boolean shouldBeActive() {
+            return true;
+        }
+    }
+
+    private static void assertMainThread(String methodName) {
+        if (!ArchTaskExecutor.getInstance().isMainThread()) {
+            throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+                    + " thread");
+        }
+    }
+}
diff --git a/lifecycle/livedata-core/src/main/java/androidx/lifecycle/MutableLiveData.java b/lifecycle/livedata-core/src/main/java/androidx/lifecycle/MutableLiveData.java
new file mode 100644
index 0000000..06014f0
--- /dev/null
+++ b/lifecycle/livedata-core/src/main/java/androidx/lifecycle/MutableLiveData.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle;
+
+/**
+ * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
+ *
+ * @param <T> The type of data hold by this instance
+ */
+@SuppressWarnings("WeakerAccess")
+public class MutableLiveData<T> extends LiveData<T> {
+    @Override
+    public void postValue(T value) {
+        super.postValue(value);
+    }
+
+    @Override
+    public void setValue(T value) {
+        super.setValue(value);
+    }
+}
diff --git a/lifecycle/livedata-core/src/main/java/androidx/lifecycle/Observer.java b/lifecycle/livedata-core/src/main/java/androidx/lifecycle/Observer.java
new file mode 100644
index 0000000..1a7130d
--- /dev/null
+++ b/lifecycle/livedata-core/src/main/java/androidx/lifecycle/Observer.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 androidx.lifecycle;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A simple callback that can receive from {@link LiveData}.
+ *
+ * @param <T> The type of the parameter
+ *
+ * @see LiveData LiveData - for a usage description.
+ */
+public interface Observer<T> {
+    /**
+     * Called when the data is changed.
+     * @param t  The new data
+     */
+    void onChanged(@Nullable T t);
+}
diff --git a/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/LiveDataTest.java b/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/LiveDataTest.java
deleted file mode 100644
index 046059b..0000000
--- a/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/LiveDataTest.java
+++ /dev/null
@@ -1,842 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.testing.InstantTaskExecutorRule;
-import android.support.annotation.Nullable;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mockito;
-
-@SuppressWarnings({"unchecked"})
-@RunWith(JUnit4.class)
-public class LiveDataTest {
-
-    @Rule
-    public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
-
-    private PublicLiveData<String> mLiveData;
-    private MethodExec mActiveObserversChanged;
-
-    private LifecycleOwner mOwner;
-    private LifecycleRegistry mRegistry;
-
-    private LifecycleOwner mOwner2;
-    private LifecycleRegistry mRegistry2;
-
-    private LifecycleOwner mOwner3;
-    private Lifecycle mLifecycle3;
-    private Observer<String> mObserver3;
-
-    private LifecycleOwner mOwner4;
-    private Lifecycle mLifecycle4;
-    private Observer<String> mObserver4;
-
-    private boolean mInObserver;
-
-    @Before
-    public void init() {
-        mLiveData = new PublicLiveData<>();
-
-        mActiveObserversChanged = mock(MethodExec.class);
-        mLiveData.activeObserversChanged = mActiveObserversChanged;
-
-        mOwner = mock(LifecycleOwner.class);
-        mRegistry = new LifecycleRegistry(mOwner);
-        when(mOwner.getLifecycle()).thenReturn(mRegistry);
-
-        mOwner2 = mock(LifecycleOwner.class);
-        mRegistry2 = new LifecycleRegistry(mOwner2);
-        when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
-
-        mInObserver = false;
-    }
-
-    @Before
-    public void initNonLifecycleRegistry() {
-        mOwner3 = mock(LifecycleOwner.class);
-        mLifecycle3 = mock(Lifecycle.class);
-        mObserver3 = (Observer<String>) mock(Observer.class);
-        when(mOwner3.getLifecycle()).thenReturn(mLifecycle3);
-
-        mOwner4 = mock(LifecycleOwner.class);
-        mLifecycle4 = mock(Lifecycle.class);
-        mObserver4 = (Observer<String>) mock(Observer.class);
-        when(mOwner4.getLifecycle()).thenReturn(mLifecycle4);
-    }
-
-    @After
-    public void removeExecutorDelegate() {
-        ArchTaskExecutor.getInstance().setDelegate(null);
-    }
-
-    @Test
-    public void testObserverToggle() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
-
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        assertThat(mLiveData.hasObservers(), is(true));
-        assertThat(mLiveData.hasActiveObservers(), is(false));
-
-        mLiveData.removeObserver(observer);
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        assertThat(mLiveData.hasObservers(), is(false));
-        assertThat(mLiveData.hasActiveObservers(), is(false));
-    }
-
-    @Test
-    public void testActiveObserverToggle() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
-
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        assertThat(mLiveData.hasObservers(), is(true));
-        assertThat(mLiveData.hasActiveObservers(), is(false));
-
-        mRegistry.handleLifecycleEvent(ON_START);
-        verify(mActiveObserversChanged).onCall(true);
-        assertThat(mLiveData.hasActiveObservers(), is(true));
-        reset(mActiveObserversChanged);
-
-        mRegistry.handleLifecycleEvent(ON_STOP);
-        verify(mActiveObserversChanged).onCall(false);
-        assertThat(mLiveData.hasActiveObservers(), is(false));
-        assertThat(mLiveData.hasObservers(), is(true));
-
-        reset(mActiveObserversChanged);
-        mRegistry.handleLifecycleEvent(ON_START);
-        verify(mActiveObserversChanged).onCall(true);
-        assertThat(mLiveData.hasActiveObservers(), is(true));
-        assertThat(mLiveData.hasObservers(), is(true));
-
-        reset(mActiveObserversChanged);
-        mLiveData.removeObserver(observer);
-        verify(mActiveObserversChanged).onCall(false);
-        assertThat(mLiveData.hasActiveObservers(), is(false));
-        assertThat(mLiveData.hasObservers(), is(false));
-
-        verifyNoMoreInteractions(mActiveObserversChanged);
-    }
-
-    @Test
-    public void testReAddSameObserverTuple() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
-        mLiveData.observe(mOwner, observer);
-        assertThat(mLiveData.hasObservers(), is(true));
-    }
-
-    @Test
-    public void testAdd2ObserversWithSameOwnerAndRemove() {
-        Observer<String> o1 = (Observer<String>) mock(Observer.class);
-        Observer<String> o2 = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, o1);
-        mLiveData.observe(mOwner, o2);
-        assertThat(mLiveData.hasObservers(), is(true));
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-
-        mRegistry.handleLifecycleEvent(ON_START);
-        verify(mActiveObserversChanged).onCall(true);
-        mLiveData.setValue("a");
-        verify(o1).onChanged("a");
-        verify(o2).onChanged("a");
-
-        mLiveData.removeObservers(mOwner);
-
-        assertThat(mLiveData.hasObservers(), is(false));
-        assertThat(mRegistry.getObserverCount(), is(0));
-    }
-
-    @Test
-    public void testAddSameObserverIn2LifecycleOwners() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-
-        mLiveData.observe(mOwner, observer);
-        Throwable throwable = null;
-        try {
-            mLiveData.observe(mOwner2, observer);
-        } catch (Throwable t) {
-            throwable = t;
-        }
-        assertThat(throwable, instanceOf(IllegalArgumentException.class));
-        //noinspection ConstantConditions
-        assertThat(throwable.getMessage(),
-                is("Cannot add the same observer with different lifecycles"));
-    }
-
-    @Test
-    public void testRemoveDestroyedObserver() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
-        mRegistry.handleLifecycleEvent(ON_START);
-        verify(mActiveObserversChanged).onCall(true);
-        assertThat(mLiveData.hasObservers(), is(true));
-        assertThat(mLiveData.hasActiveObservers(), is(true));
-
-        reset(mActiveObserversChanged);
-
-        mRegistry.handleLifecycleEvent(ON_DESTROY);
-        assertThat(mLiveData.hasObservers(), is(false));
-        assertThat(mLiveData.hasActiveObservers(), is(false));
-        verify(mActiveObserversChanged).onCall(false);
-    }
-
-    @Test
-    public void testInactiveRegistry() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mRegistry.handleLifecycleEvent(ON_DESTROY);
-        mLiveData.observe(mOwner, observer);
-        assertThat(mLiveData.hasObservers(), is(false));
-    }
-
-    @Test
-    public void testNotifyActiveInactive() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mRegistry.handleLifecycleEvent(ON_CREATE);
-        mLiveData.observe(mOwner, observer);
-        mLiveData.setValue("a");
-        verify(observer, never()).onChanged(anyString());
-        mRegistry.handleLifecycleEvent(ON_START);
-        verify(observer).onChanged("a");
-
-        mLiveData.setValue("b");
-        verify(observer).onChanged("b");
-
-        mRegistry.handleLifecycleEvent(ON_STOP);
-        mLiveData.setValue("c");
-        verify(observer, never()).onChanged("c");
-
-        mRegistry.handleLifecycleEvent(ON_START);
-        verify(observer).onChanged("c");
-
-        reset(observer);
-        mRegistry.handleLifecycleEvent(ON_STOP);
-        mRegistry.handleLifecycleEvent(ON_START);
-        verify(observer, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void testStopObservingOwner_onDestroy() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mRegistry.handleLifecycleEvent(ON_CREATE);
-        mLiveData.observe(mOwner, observer);
-        assertThat(mRegistry.getObserverCount(), is(1));
-        mRegistry.handleLifecycleEvent(ON_DESTROY);
-        assertThat(mRegistry.getObserverCount(), is(0));
-    }
-
-    @Test
-    public void testStopObservingOwner_onStopObserving() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mRegistry.handleLifecycleEvent(ON_CREATE);
-        mLiveData.observe(mOwner, observer);
-        assertThat(mRegistry.getObserverCount(), is(1));
-
-        mLiveData.removeObserver(observer);
-        assertThat(mRegistry.getObserverCount(), is(0));
-    }
-
-    @Test
-    public void testActiveChangeInCallback() {
-        mRegistry.handleLifecycleEvent(ON_START);
-        Observer<String> observer1 = spy(new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                mRegistry.handleLifecycleEvent(ON_STOP);
-                assertThat(mLiveData.hasObservers(), is(true));
-                assertThat(mLiveData.hasActiveObservers(), is(false));
-            }
-        });
-        final Observer observer2 = mock(Observer.class);
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner, observer2);
-        mLiveData.setValue("bla");
-        verify(observer1).onChanged(anyString());
-        verify(observer2, Mockito.never()).onChanged(anyString());
-        assertThat(mLiveData.hasObservers(), is(true));
-        assertThat(mLiveData.hasActiveObservers(), is(false));
-    }
-
-    @Test
-    public void testActiveChangeInCallback2() {
-        Observer<String> observer1 = spy(new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                assertThat(mInObserver, is(false));
-                mInObserver = true;
-                mRegistry.handleLifecycleEvent(ON_START);
-                assertThat(mLiveData.hasActiveObservers(), is(true));
-                mInObserver = false;
-            }
-        });
-        final Observer observer2 = spy(new FailReentranceObserver());
-        mLiveData.observeForever(observer1);
-        mLiveData.observe(mOwner, observer2);
-        mLiveData.setValue("bla");
-        verify(observer1).onChanged(anyString());
-        verify(observer2).onChanged(anyString());
-        assertThat(mLiveData.hasObservers(), is(true));
-        assertThat(mLiveData.hasActiveObservers(), is(true));
-    }
-
-    @Test
-    public void testObserverRemovalInCallback() {
-        mRegistry.handleLifecycleEvent(ON_START);
-        Observer<String> observer = spy(new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                assertThat(mLiveData.hasObservers(), is(true));
-                mLiveData.removeObserver(this);
-                assertThat(mLiveData.hasObservers(), is(false));
-            }
-        });
-        mLiveData.observe(mOwner, observer);
-        mLiveData.setValue("bla");
-        verify(observer).onChanged(anyString());
-        assertThat(mLiveData.hasObservers(), is(false));
-    }
-
-    @Test
-    public void testObserverAdditionInCallback() {
-        mRegistry.handleLifecycleEvent(ON_START);
-        final Observer observer2 = spy(new FailReentranceObserver());
-        Observer<String> observer1 = spy(new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                assertThat(mInObserver, is(false));
-                mInObserver = true;
-                mLiveData.observe(mOwner, observer2);
-                assertThat(mLiveData.hasObservers(), is(true));
-                assertThat(mLiveData.hasActiveObservers(), is(true));
-                mInObserver = false;
-            }
-        });
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.setValue("bla");
-        verify(observer1).onChanged(anyString());
-        verify(observer2).onChanged(anyString());
-        assertThat(mLiveData.hasObservers(), is(true));
-        assertThat(mLiveData.hasActiveObservers(), is(true));
-    }
-
-    @Test
-    public void testObserverWithoutLifecycleOwner() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.setValue("boring");
-        mLiveData.observeForever(observer);
-        verify(mActiveObserversChanged).onCall(true);
-        verify(observer).onChanged("boring");
-        mLiveData.setValue("tihs");
-        verify(observer).onChanged("tihs");
-        mLiveData.removeObserver(observer);
-        verify(mActiveObserversChanged).onCall(false);
-        mLiveData.setValue("boring");
-        reset(observer);
-        verify(observer, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void testSetValueDuringSetValue() {
-        mRegistry.handleLifecycleEvent(ON_START);
-        final Observer observer1 = spy(new Observer<String>() {
-            @Override
-            public void onChanged(String o) {
-                assertThat(mInObserver, is(false));
-                mInObserver = true;
-                if (o.equals(("bla"))) {
-                    mLiveData.setValue("gt");
-                }
-                mInObserver = false;
-            }
-        });
-        final Observer observer2 = spy(new FailReentranceObserver());
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner, observer2);
-        mLiveData.setValue("bla");
-        verify(observer1, Mockito.atMost(2)).onChanged("gt");
-        verify(observer2, Mockito.atMost(2)).onChanged("gt");
-    }
-
-    @Test
-    public void testRemoveDuringSetValue() {
-        mRegistry.handleLifecycleEvent(ON_START);
-        final Observer observer1 = spy(new Observer<String>() {
-            @Override
-            public void onChanged(String o) {
-                mLiveData.removeObserver(this);
-            }
-        });
-        Observer<String> observer2 = (Observer<String>) mock(Observer.class);
-        mLiveData.observeForever(observer1);
-        mLiveData.observe(mOwner, observer2);
-        mLiveData.setValue("gt");
-        verify(observer2).onChanged("gt");
-    }
-
-    @Test
-    public void testDataChangeDuringStateChange() {
-        mRegistry.handleLifecycleEvent(ON_START);
-        mRegistry.addObserver(new LifecycleObserver() {
-            @OnLifecycleEvent(ON_STOP)
-            public void onStop() {
-                // change data in onStop, observer should not be called!
-                mLiveData.setValue("b");
-            }
-        });
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.setValue("a");
-        mLiveData.observe(mOwner, observer);
-        verify(observer).onChanged("a");
-        mRegistry.handleLifecycleEvent(ON_PAUSE);
-        mRegistry.handleLifecycleEvent(ON_STOP);
-        verify(observer, never()).onChanged("b");
-
-        mRegistry.handleLifecycleEvent(ON_RESUME);
-        verify(observer).onChanged("b");
-    }
-
-    @Test
-    public void testNotCallInactiveWithObserveForever() {
-        mRegistry.handleLifecycleEvent(ON_START);
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        Observer<String> observer2 = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
-        mLiveData.observeForever(observer2);
-        verify(mActiveObserversChanged).onCall(true);
-        reset(mActiveObserversChanged);
-        mRegistry.handleLifecycleEvent(ON_STOP);
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        mRegistry.handleLifecycleEvent(ON_START);
-        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();
-    }
-
-    @Test
-    public void setValue_neverActive_observerOnChangedNotCalled() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
-
-        mLiveData.setValue("1");
-
-        verify(observer, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
-
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
-
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
-        mLiveData.setValue("1");
-
-        verify(observer1).onChanged("1");
-        verify(observer2).onChanged("1");
-    }
-
-    @Test
-    public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
-
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
-
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
-        mLiveData.setValue("1");
-
-        verify(observer1).onChanged("1");
-        verify(observer2, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
-
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
-
-        mLiveData.setValue("1");
-
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
-        verify(observer1).onChanged("1");
-        verify(observer1).onChanged("1");
-    }
-
-    @Test
-    public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
-
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
-
-        mLiveData.setValue("1");
-
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
-        verify(observer1).onChanged("1");
-        verify(observer2, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
-
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
-
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
-        verify(mActiveObserversChanged).onCall(true);
-    }
-
-    @Test
-    public void setValue_twoObserversOneStopped_liveDataStaysActive() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
-
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
-
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
-        verify(mActiveObserversChanged).onCall(true);
-
-        reset(mActiveObserversChanged);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-    }
-
-    @Test
-    public void setValue_lifecycleIsCreatedNoEvent_liveDataBecomesInactiveAndObserverNotCalled() {
-
-        // Arrange.
-
-        mLiveData.observe(mOwner3, mObserver3);
-
-        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
-
-        reset(mActiveObserversChanged);
-        reset(mObserver3);
-
-        // Act.
-
-        mLiveData.setValue("1");
-
-        // Assert.
-
-        verify(mActiveObserversChanged).onCall(false);
-        verify(mObserver3, never()).onChanged(anyString());
-    }
-
-    /*
-     * Arrange: LiveData was made inactive via SetValue (because the Lifecycle it was
-     * observing was in the CREATED state and no event was dispatched).
-     * Act: Lifecycle enters Started state and dispatches event.
-     * Assert: LiveData becomes active and dispatches new value to observer.
-     */
-    @Test
-    public void test_liveDataInactiveViaSetValueThenLifecycleResumes() {
-
-        // Arrange.
-
-        mLiveData.observe(mOwner3, mObserver3);
-
-        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
-        mLiveData.setValue("1");
-
-        reset(mActiveObserversChanged);
-        reset(mObserver3);
-
-        // Act.
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
-
-        // Assert.
-
-        verify(mActiveObserversChanged).onCall(true);
-        verify(mObserver3).onChanged("1");
-    }
-
-    /*
-     * Arrange: One of two Lifecycles enter the CREATED state without sending an event.
-     * Act: Lifecycle's setValue method is called with new value.
-     * Assert: LiveData stays active and new value is dispatched to Lifecycle that is still at least
-     * STARTED.
-     */
-    @Test
-    public void setValue_oneOfTwoLifecyclesAreCreatedNoEvent() {
-
-        // Arrange.
-
-        mLiveData.observe(mOwner3, mObserver3);
-        mLiveData.observe(mOwner4, mObserver4);
-
-        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
-        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
-        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
-
-        reset(mActiveObserversChanged);
-        reset(mObserver3);
-        reset(mObserver4);
-
-        // Act.
-
-        mLiveData.setValue("1");
-
-        // Assert.
-
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        verify(mObserver3, never()).onChanged(anyString());
-        verify(mObserver4).onChanged("1");
-    }
-
-    /*
-     * Arrange: Two observed Lifecycles enter the CREATED state without sending an event.
-     * Act: Lifecycle's setValue method is called with new value.
-     * Assert: LiveData becomes inactive and nothing is dispatched to either observer.
-     */
-    @Test
-    public void setValue_twoLifecyclesAreCreatedNoEvent() {
-
-        // Arrange.
-
-        mLiveData.observe(mOwner3, mObserver3);
-        mLiveData.observe(mOwner4, mObserver4);
-
-        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
-        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
-        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
-        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
-
-        reset(mActiveObserversChanged);
-        reset(mObserver3);
-        reset(mObserver4);
-
-        // Act.
-
-        mLiveData.setValue("1");
-
-        // Assert.
-
-        verify(mActiveObserversChanged).onCall(false);
-        verify(mObserver3, never()).onChanged(anyString());
-        verify(mObserver3, never()).onChanged(anyString());
-    }
-
-    /*
-     * Arrange: LiveData was made inactive via SetValue (because both Lifecycles it was
-     * observing were in the CREATED state and no event was dispatched).
-     * Act: One Lifecycle enters STARTED state and dispatches lifecycle event.
-     * Assert: LiveData becomes active and dispatches new value to observer associated with started
-     * Lifecycle.
-     */
-    @Test
-    public void test_liveDataInactiveViaSetValueThenOneLifecycleResumes() {
-
-        // Arrange.
-
-        mLiveData.observe(mOwner3, mObserver3);
-        mLiveData.observe(mOwner4, mObserver4);
-
-        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
-        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
-        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
-        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
-
-        mLiveData.setValue("1");
-
-        reset(mActiveObserversChanged);
-        reset(mObserver3);
-        reset(mObserver4);
-
-        // Act.
-
-        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
-        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
-
-        // Assert.
-
-        verify(mActiveObserversChanged).onCall(true);
-        verify(mObserver3).onChanged("1");
-        verify(mObserver4, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void nestedForeverObserver() {
-        mLiveData.setValue(".");
-        mLiveData.observeForever(new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                mLiveData.observeForever(mock(Observer.class));
-                mLiveData.removeObserver(this);
-            }
-        });
-        verify(mActiveObserversChanged, only()).onCall(true);
-    }
-
-    @Test
-    public void readdForeverObserver() {
-        Observer observer = mock(Observer.class);
-        mLiveData.observeForever(observer);
-        mLiveData.observeForever(observer);
-        mLiveData.removeObserver(observer);
-        assertThat(mLiveData.hasObservers(), is(false));
-    }
-
-    private GenericLifecycleObserver getGenericLifecycleObserver(Lifecycle lifecycle) {
-        ArgumentCaptor<GenericLifecycleObserver> captor =
-                ArgumentCaptor.forClass(GenericLifecycleObserver.class);
-        verify(lifecycle).addObserver(captor.capture());
-        return (captor.getValue());
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class PublicLiveData<T> extends LiveData<T> {
-        // cannot spy due to internal calls
-        public MethodExec activeObserversChanged;
-
-        @Override
-        protected void onActive() {
-            if (activeObserversChanged != null) {
-                activeObserversChanged.onCall(true);
-            }
-        }
-
-        @Override
-        protected void onInactive() {
-            if (activeObserversChanged != null) {
-                activeObserversChanged.onCall(false);
-            }
-        }
-    }
-
-    private class FailReentranceObserver<T> implements Observer<T> {
-        @Override
-        public void onChanged(@Nullable T t) {
-            assertThat(mInObserver, is(false));
-        }
-    }
-
-    interface MethodExec {
-        void onCall(boolean value);
-    }
-}
diff --git a/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java b/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java
deleted file mode 100644
index 3366641..0000000
--- a/lifecycle/livedata-core/src/test/java/android/arch/lifecycle/ThreadedLiveDataTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-
-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 android.arch.core.executor.JunitTaskExecutorRule;
-import android.arch.core.executor.TaskExecutor;
-import android.support.annotation.Nullable;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(JUnit4.class)
-public class ThreadedLiveDataTest {
-
-    private static final int TIMEOUT_SECS = 3;
-
-    @Rule
-    public JunitTaskExecutorRule mTaskExecutorRule = new JunitTaskExecutorRule(1, false);
-
-    private LiveData<String> mLiveData;
-    private LifecycleOwner mLifecycleOwner;
-    private LifecycleRegistry mRegistry;
-
-    @Before
-    public void init() {
-        mLiveData = new MutableLiveData<>();
-        mLifecycleOwner = mock(LifecycleOwner.class);
-        mRegistry = new LifecycleRegistry(mLifecycleOwner);
-        when(mLifecycleOwner.getLifecycle()).thenReturn(mRegistry);
-    }
-
-    @Test
-    public void testPostValue() throws InterruptedException {
-        final TaskExecutor taskExecutor = mTaskExecutorRule.getTaskExecutor();
-        final CountDownLatch finishTestLatch = new CountDownLatch(1);
-        final Observer<String> observer = new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String newValue) {
-                try {
-                    assertThat(taskExecutor.isMainThread(), is(true));
-                    assertThat(newValue, is("success"));
-                } finally {
-                    finishTestLatch.countDown();
-                }
-            }
-        };
-        taskExecutor.executeOnMainThread(new Runnable() {
-            @Override
-            public void run() {
-                mRegistry.handleLifecycleEvent(ON_START);
-                mLiveData.observe(mLifecycleOwner, observer);
-                final CountDownLatch latch = new CountDownLatch(1);
-                taskExecutor.executeOnDiskIO(new Runnable() {
-                    @Override
-                    public void run() {
-                        mLiveData.postValue("fail");
-                        mLiveData.postValue("success");
-                        latch.countDown();
-                    }
-                });
-                try {
-                    assertThat(latch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        });
-        assertThat(finishTestLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
-    }
-}
diff --git a/lifecycle/livedata-core/src/test/java/androidx/lifecycle/LiveDataTest.java b/lifecycle/livedata-core/src/test/java/androidx/lifecycle/LiveDataTest.java
new file mode 100644
index 0000000..741ee66
--- /dev/null
+++ b/lifecycle/livedata-core/src/test/java/androidx/lifecycle/LiveDataTest.java
@@ -0,0 +1,842 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+@SuppressWarnings({"unchecked"})
+@RunWith(JUnit4.class)
+public class LiveDataTest {
+
+    @Rule
+    public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
+
+    private PublicLiveData<String> mLiveData;
+    private MethodExec mActiveObserversChanged;
+
+    private LifecycleOwner mOwner;
+    private LifecycleRegistry mRegistry;
+
+    private LifecycleOwner mOwner2;
+    private LifecycleRegistry mRegistry2;
+
+    private LifecycleOwner mOwner3;
+    private Lifecycle mLifecycle3;
+    private Observer<String> mObserver3;
+
+    private LifecycleOwner mOwner4;
+    private Lifecycle mLifecycle4;
+    private Observer<String> mObserver4;
+
+    private boolean mInObserver;
+
+    @Before
+    public void init() {
+        mLiveData = new PublicLiveData<>();
+
+        mActiveObserversChanged = mock(MethodExec.class);
+        mLiveData.activeObserversChanged = mActiveObserversChanged;
+
+        mOwner = mock(LifecycleOwner.class);
+        mRegistry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(mRegistry);
+
+        mOwner2 = mock(LifecycleOwner.class);
+        mRegistry2 = new LifecycleRegistry(mOwner2);
+        when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
+
+        mInObserver = false;
+    }
+
+    @Before
+    public void initNonLifecycleRegistry() {
+        mOwner3 = mock(LifecycleOwner.class);
+        mLifecycle3 = mock(Lifecycle.class);
+        mObserver3 = (Observer<String>) mock(Observer.class);
+        when(mOwner3.getLifecycle()).thenReturn(mLifecycle3);
+
+        mOwner4 = mock(LifecycleOwner.class);
+        mLifecycle4 = mock(Lifecycle.class);
+        mObserver4 = (Observer<String>) mock(Observer.class);
+        when(mOwner4.getLifecycle()).thenReturn(mLifecycle4);
+    }
+
+    @After
+    public void removeExecutorDelegate() {
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    @Test
+    public void testObserverToggle() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+
+        mLiveData.removeObserver(observer);
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        assertThat(mLiveData.hasObservers(), is(false));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+    }
+
+    @Test
+    public void testActiveObserverToggle() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+        reset(mActiveObserversChanged);
+
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        verify(mActiveObserversChanged).onCall(false);
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        assertThat(mLiveData.hasObservers(), is(true));
+
+        reset(mActiveObserversChanged);
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+        assertThat(mLiveData.hasObservers(), is(true));
+
+        reset(mActiveObserversChanged);
+        mLiveData.removeObserver(observer);
+        verify(mActiveObserversChanged).onCall(false);
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        assertThat(mLiveData.hasObservers(), is(false));
+
+        verifyNoMoreInteractions(mActiveObserversChanged);
+    }
+
+    @Test
+    public void testReAddSameObserverTuple() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mLiveData.hasObservers(), is(true));
+    }
+
+    @Test
+    public void testAdd2ObserversWithSameOwnerAndRemove() {
+        Observer<String> o1 = (Observer<String>) mock(Observer.class);
+        Observer<String> o2 = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, o1);
+        mLiveData.observe(mOwner, o2);
+        assertThat(mLiveData.hasObservers(), is(true));
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        mLiveData.setValue("a");
+        verify(o1).onChanged("a");
+        verify(o2).onChanged("a");
+
+        mLiveData.removeObservers(mOwner);
+
+        assertThat(mLiveData.hasObservers(), is(false));
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void testAddSameObserverIn2LifecycleOwners() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer);
+        Throwable throwable = null;
+        try {
+            mLiveData.observe(mOwner2, observer);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertThat(throwable, instanceOf(IllegalArgumentException.class));
+        //noinspection ConstantConditions
+        assertThat(throwable.getMessage(),
+                is("Cannot add the same observer with different lifecycles"));
+    }
+
+    @Test
+    public void testRemoveDestroyedObserver() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(mActiveObserversChanged).onCall(true);
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+
+        reset(mActiveObserversChanged);
+
+        mRegistry.handleLifecycleEvent(ON_DESTROY);
+        assertThat(mLiveData.hasObservers(), is(false));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+        verify(mActiveObserversChanged).onCall(false);
+    }
+
+    @Test
+    public void testInactiveRegistry() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_DESTROY);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mLiveData.hasObservers(), is(false));
+    }
+
+    @Test
+    public void testNotifyActiveInactive() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_CREATE);
+        mLiveData.observe(mOwner, observer);
+        mLiveData.setValue("a");
+        verify(observer, never()).onChanged(anyString());
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(observer).onChanged("a");
+
+        mLiveData.setValue("b");
+        verify(observer).onChanged("b");
+
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        mLiveData.setValue("c");
+        verify(observer, never()).onChanged("c");
+
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(observer).onChanged("c");
+
+        reset(observer);
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        mRegistry.handleLifecycleEvent(ON_START);
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testStopObservingOwner_onDestroy() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_CREATE);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mRegistry.getObserverCount(), is(1));
+        mRegistry.handleLifecycleEvent(ON_DESTROY);
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void testStopObservingOwner_onStopObserving() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mRegistry.handleLifecycleEvent(ON_CREATE);
+        mLiveData.observe(mOwner, observer);
+        assertThat(mRegistry.getObserverCount(), is(1));
+
+        mLiveData.removeObserver(observer);
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void testActiveChangeInCallback() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        Observer<String> observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mRegistry.handleLifecycleEvent(ON_STOP);
+                assertThat(mLiveData.hasObservers(), is(true));
+                assertThat(mLiveData.hasActiveObservers(), is(false));
+            }
+        });
+        final Observer observer2 = mock(Observer.class);
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner, observer2);
+        mLiveData.setValue("bla");
+        verify(observer1).onChanged(anyString());
+        verify(observer2, Mockito.never()).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(false));
+    }
+
+    @Test
+    public void testActiveChangeInCallback2() {
+        Observer<String> observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                assertThat(mInObserver, is(false));
+                mInObserver = true;
+                mRegistry.handleLifecycleEvent(ON_START);
+                assertThat(mLiveData.hasActiveObservers(), is(true));
+                mInObserver = false;
+            }
+        });
+        final Observer observer2 = spy(new FailReentranceObserver());
+        mLiveData.observeForever(observer1);
+        mLiveData.observe(mOwner, observer2);
+        mLiveData.setValue("bla");
+        verify(observer1).onChanged(anyString());
+        verify(observer2).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+    }
+
+    @Test
+    public void testObserverRemovalInCallback() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        Observer<String> observer = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                assertThat(mLiveData.hasObservers(), is(true));
+                mLiveData.removeObserver(this);
+                assertThat(mLiveData.hasObservers(), is(false));
+            }
+        });
+        mLiveData.observe(mOwner, observer);
+        mLiveData.setValue("bla");
+        verify(observer).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(false));
+    }
+
+    @Test
+    public void testObserverAdditionInCallback() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        final Observer observer2 = spy(new FailReentranceObserver());
+        Observer<String> observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                assertThat(mInObserver, is(false));
+                mInObserver = true;
+                mLiveData.observe(mOwner, observer2);
+                assertThat(mLiveData.hasObservers(), is(true));
+                assertThat(mLiveData.hasActiveObservers(), is(true));
+                mInObserver = false;
+            }
+        });
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.setValue("bla");
+        verify(observer1).onChanged(anyString());
+        verify(observer2).onChanged(anyString());
+        assertThat(mLiveData.hasObservers(), is(true));
+        assertThat(mLiveData.hasActiveObservers(), is(true));
+    }
+
+    @Test
+    public void testObserverWithoutLifecycleOwner() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.setValue("boring");
+        mLiveData.observeForever(observer);
+        verify(mActiveObserversChanged).onCall(true);
+        verify(observer).onChanged("boring");
+        mLiveData.setValue("tihs");
+        verify(observer).onChanged("tihs");
+        mLiveData.removeObserver(observer);
+        verify(mActiveObserversChanged).onCall(false);
+        mLiveData.setValue("boring");
+        reset(observer);
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testSetValueDuringSetValue() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        final Observer observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(String o) {
+                assertThat(mInObserver, is(false));
+                mInObserver = true;
+                if (o.equals(("bla"))) {
+                    mLiveData.setValue("gt");
+                }
+                mInObserver = false;
+            }
+        });
+        final Observer observer2 = spy(new FailReentranceObserver());
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner, observer2);
+        mLiveData.setValue("bla");
+        verify(observer1, Mockito.atMost(2)).onChanged("gt");
+        verify(observer2, Mockito.atMost(2)).onChanged("gt");
+    }
+
+    @Test
+    public void testRemoveDuringSetValue() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        final Observer observer1 = spy(new Observer<String>() {
+            @Override
+            public void onChanged(String o) {
+                mLiveData.removeObserver(this);
+            }
+        });
+        Observer<String> observer2 = (Observer<String>) mock(Observer.class);
+        mLiveData.observeForever(observer1);
+        mLiveData.observe(mOwner, observer2);
+        mLiveData.setValue("gt");
+        verify(observer2).onChanged("gt");
+    }
+
+    @Test
+    public void testDataChangeDuringStateChange() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        mRegistry.addObserver(new LifecycleObserver() {
+            @OnLifecycleEvent(ON_STOP)
+            public void onStop() {
+                // change data in onStop, observer should not be called!
+                mLiveData.setValue("b");
+            }
+        });
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.setValue("a");
+        mLiveData.observe(mOwner, observer);
+        verify(observer).onChanged("a");
+        mRegistry.handleLifecycleEvent(ON_PAUSE);
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        verify(observer, never()).onChanged("b");
+
+        mRegistry.handleLifecycleEvent(ON_RESUME);
+        verify(observer).onChanged("b");
+    }
+
+    @Test
+    public void testNotCallInactiveWithObserveForever() {
+        mRegistry.handleLifecycleEvent(ON_START);
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        Observer<String> observer2 = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+        mLiveData.observeForever(observer2);
+        verify(mActiveObserversChanged).onCall(true);
+        reset(mActiveObserversChanged);
+        mRegistry.handleLifecycleEvent(ON_STOP);
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        mRegistry.handleLifecycleEvent(ON_START);
+        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();
+    }
+
+    @Test
+    public void setValue_neverActive_observerOnChangedNotCalled() {
+        Observer<String> observer = (Observer<String>) mock(Observer.class);
+        mLiveData.observe(mOwner, observer);
+
+        mLiveData.setValue("1");
+
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        mLiveData.setValue("1");
+
+        verify(observer1).onChanged("1");
+        verify(observer2).onChanged("1");
+    }
+
+    @Test
+    public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        mLiveData.setValue("1");
+
+        verify(observer1).onChanged("1");
+        verify(observer2, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mLiveData.setValue("1");
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(observer1).onChanged("1");
+        verify(observer1).onChanged("1");
+    }
+
+    @Test
+    public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mLiveData.setValue("1");
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(observer1).onChanged("1");
+        verify(observer2, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mActiveObserversChanged).onCall(true);
+    }
+
+    @Test
+    public void setValue_twoObserversOneStopped_liveDataStaysActive() {
+        Observer<String> observer1 = mock(Observer.class);
+        Observer<String> observer2 = mock(Observer.class);
+
+        mLiveData.observe(mOwner, observer1);
+        mLiveData.observe(mOwner2, observer2);
+
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mActiveObserversChanged).onCall(true);
+
+        reset(mActiveObserversChanged);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+    }
+
+    @Test
+    public void setValue_lifecycleIsCreatedNoEvent_liveDataBecomesInactiveAndObserverNotCalled() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+
+        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+
+        // Act.
+
+        mLiveData.setValue("1");
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(false);
+        verify(mObserver3, never()).onChanged(anyString());
+    }
+
+    /*
+     * Arrange: LiveData was made inactive via SetValue (because the Lifecycle it was
+     * observing was in the CREATED state and no event was dispatched).
+     * Act: Lifecycle enters Started state and dispatches event.
+     * Assert: LiveData becomes active and dispatches new value to observer.
+     */
+    @Test
+    public void test_liveDataInactiveViaSetValueThenLifecycleResumes() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+
+        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+        mLiveData.setValue("1");
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+
+        // Act.
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(true);
+        verify(mObserver3).onChanged("1");
+    }
+
+    /*
+     * Arrange: One of two Lifecycles enter the CREATED state without sending an event.
+     * Act: Lifecycle's setValue method is called with new value.
+     * Assert: LiveData stays active and new value is dispatched to Lifecycle that is still at least
+     * STARTED.
+     */
+    @Test
+    public void setValue_oneOfTwoLifecyclesAreCreatedNoEvent() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        mLiveData.setValue("1");
+
+        // Assert.
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        verify(mObserver3, never()).onChanged(anyString());
+        verify(mObserver4).onChanged("1");
+    }
+
+    /*
+     * Arrange: Two observed Lifecycles enter the CREATED state without sending an event.
+     * Act: Lifecycle's setValue method is called with new value.
+     * Assert: LiveData becomes inactive and nothing is dispatched to either observer.
+     */
+    @Test
+    public void setValue_twoLifecyclesAreCreatedNoEvent() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        mLiveData.setValue("1");
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(false);
+        verify(mObserver3, never()).onChanged(anyString());
+        verify(mObserver3, never()).onChanged(anyString());
+    }
+
+    /*
+     * Arrange: LiveData was made inactive via SetValue (because both Lifecycles it was
+     * observing were in the CREATED state and no event was dispatched).
+     * Act: One Lifecycle enters STARTED state and dispatches lifecycle event.
+     * Assert: LiveData becomes active and dispatches new value to observer associated with started
+     * Lifecycle.
+     */
+    @Test
+    public void test_liveDataInactiveViaSetValueThenOneLifecycleResumes() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        mLiveData.setValue("1");
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(true);
+        verify(mObserver3).onChanged("1");
+        verify(mObserver4, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void nestedForeverObserver() {
+        mLiveData.setValue(".");
+        mLiveData.observeForever(new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mLiveData.observeForever(mock(Observer.class));
+                mLiveData.removeObserver(this);
+            }
+        });
+        verify(mActiveObserversChanged, only()).onCall(true);
+    }
+
+    @Test
+    public void readdForeverObserver() {
+        Observer observer = mock(Observer.class);
+        mLiveData.observeForever(observer);
+        mLiveData.observeForever(observer);
+        mLiveData.removeObserver(observer);
+        assertThat(mLiveData.hasObservers(), is(false));
+    }
+
+    private GenericLifecycleObserver getGenericLifecycleObserver(Lifecycle lifecycle) {
+        ArgumentCaptor<GenericLifecycleObserver> captor =
+                ArgumentCaptor.forClass(GenericLifecycleObserver.class);
+        verify(lifecycle).addObserver(captor.capture());
+        return (captor.getValue());
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class PublicLiveData<T> extends LiveData<T> {
+        // cannot spy due to internal calls
+        public MethodExec activeObserversChanged;
+
+        @Override
+        protected void onActive() {
+            if (activeObserversChanged != null) {
+                activeObserversChanged.onCall(true);
+            }
+        }
+
+        @Override
+        protected void onInactive() {
+            if (activeObserversChanged != null) {
+                activeObserversChanged.onCall(false);
+            }
+        }
+    }
+
+    private class FailReentranceObserver<T> implements Observer<T> {
+        @Override
+        public void onChanged(@Nullable T t) {
+            assertThat(mInObserver, is(false));
+        }
+    }
+
+    interface MethodExec {
+        void onCall(boolean value);
+    }
+}
diff --git a/lifecycle/livedata-core/src/test/java/androidx/lifecycle/ThreadedLiveDataTest.java b/lifecycle/livedata-core/src/test/java/androidx/lifecycle/ThreadedLiveDataTest.java
new file mode 100644
index 0000000..19dcd21
--- /dev/null
+++ b/lifecycle/livedata-core/src/test/java/androidx/lifecycle/ThreadedLiveDataTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+
+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 androidx.annotation.Nullable;
+import androidx.arch.core.executor.JunitTaskExecutorRule;
+import androidx.arch.core.executor.TaskExecutor;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class ThreadedLiveDataTest {
+
+    private static final int TIMEOUT_SECS = 3;
+
+    @Rule
+    public JunitTaskExecutorRule mTaskExecutorRule = new JunitTaskExecutorRule(1, false);
+
+    private LiveData<String> mLiveData;
+    private LifecycleOwner mLifecycleOwner;
+    private LifecycleRegistry mRegistry;
+
+    @Before
+    public void init() {
+        mLiveData = new MutableLiveData<>();
+        mLifecycleOwner = mock(LifecycleOwner.class);
+        mRegistry = new LifecycleRegistry(mLifecycleOwner);
+        when(mLifecycleOwner.getLifecycle()).thenReturn(mRegistry);
+    }
+
+    @Test
+    public void testPostValue() throws InterruptedException {
+        final TaskExecutor taskExecutor = mTaskExecutorRule.getTaskExecutor();
+        final CountDownLatch finishTestLatch = new CountDownLatch(1);
+        final Observer<String> observer = new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String newValue) {
+                try {
+                    assertThat(taskExecutor.isMainThread(), is(true));
+                    assertThat(newValue, is("success"));
+                } finally {
+                    finishTestLatch.countDown();
+                }
+            }
+        };
+        taskExecutor.executeOnMainThread(new Runnable() {
+            @Override
+            public void run() {
+                mRegistry.handleLifecycleEvent(ON_START);
+                mLiveData.observe(mLifecycleOwner, observer);
+                final CountDownLatch latch = new CountDownLatch(1);
+                taskExecutor.executeOnDiskIO(new Runnable() {
+                    @Override
+                    public void run() {
+                        mLiveData.postValue("fail");
+                        mLiveData.postValue("success");
+                        latch.countDown();
+                    }
+                });
+                try {
+                    assertThat(latch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        assertThat(finishTestLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS), is(true));
+    }
+}
diff --git a/lifecycle/livedata/api/current.txt b/lifecycle/livedata/api/current.txt
index 13f8154..88ef93c 100644
--- a/lifecycle/livedata/api/current.txt
+++ b/lifecycle/livedata/api/current.txt
@@ -1,14 +1,14 @@
-package android.arch.lifecycle {
+package androidx.lifecycle {
 
-  public class MediatorLiveData<T> extends android.arch.lifecycle.MutableLiveData {
+  public class MediatorLiveData<T> extends androidx.lifecycle.MutableLiveData {
     ctor public MediatorLiveData();
-    method public <S> void addSource(android.arch.lifecycle.LiveData<S>, android.arch.lifecycle.Observer<? super S>);
-    method public <S> void removeSource(android.arch.lifecycle.LiveData<S>);
+    method public <S> void addSource(androidx.lifecycle.LiveData<S>, androidx.lifecycle.Observer<? super S>);
+    method public <S> void removeSource(androidx.lifecycle.LiveData<S>);
   }
 
   public class Transformations {
-    method public static <X, Y> android.arch.lifecycle.LiveData<Y> map(android.arch.lifecycle.LiveData<X>, android.arch.core.util.Function<X, Y>);
-    method public static <X, Y> android.arch.lifecycle.LiveData<Y> switchMap(android.arch.lifecycle.LiveData<X>, android.arch.core.util.Function<X, android.arch.lifecycle.LiveData<Y>>);
+    method public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X, Y>);
+    method public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X, androidx.lifecycle.LiveData<Y>>);
   }
 
 }
diff --git a/lifecycle/livedata/api/1.1.0.txt b/lifecycle/livedata/api_legacy/1.1.0.txt
similarity index 100%
rename from lifecycle/livedata/api/1.1.0.txt
rename to lifecycle/livedata/api_legacy/1.1.0.txt
diff --git a/lifecycle/livedata/api_legacy/current.txt b/lifecycle/livedata/api_legacy/current.txt
new file mode 100644
index 0000000..13f8154
--- /dev/null
+++ b/lifecycle/livedata/api_legacy/current.txt
@@ -0,0 +1,15 @@
+package android.arch.lifecycle {
+
+  public class MediatorLiveData<T> extends android.arch.lifecycle.MutableLiveData {
+    ctor public MediatorLiveData();
+    method public <S> void addSource(android.arch.lifecycle.LiveData<S>, android.arch.lifecycle.Observer<? super S>);
+    method public <S> void removeSource(android.arch.lifecycle.LiveData<S>);
+  }
+
+  public class Transformations {
+    method public static <X, Y> android.arch.lifecycle.LiveData<Y> map(android.arch.lifecycle.LiveData<X>, android.arch.core.util.Function<X, Y>);
+    method public static <X, Y> android.arch.lifecycle.LiveData<Y> switchMap(android.arch.lifecycle.LiveData<X>, android.arch.core.util.Function<X, android.arch.lifecycle.LiveData<Y>>);
+  }
+
+}
+
diff --git a/lifecycle/livedata/build.gradle b/lifecycle/livedata/build.gradle
index cf3b50c..d797335 100644
--- a/lifecycle/livedata/build.gradle
+++ b/lifecycle/livedata/build.gradle
@@ -24,11 +24,11 @@
 }
 
 dependencies {
-    implementation(project(":arch:common"))
-    api(project(":arch:runtime"))
-    api(project(":lifecycle:livedata-core"))
+    implementation(project(":arch:core-common"))
+    api(project(":arch:core-runtime"))
+    api(project(":lifecycle:lifecycle-livedata-core"))
 
-    testImplementation(project(":lifecycle:runtime"))
+    testImplementation(project(":lifecycle:lifecycle-runtime"))
     testImplementation(project(":arch:core-testing"))
     testImplementation(JUNIT)
     testImplementation(MOCKITO_CORE)
diff --git a/lifecycle/livedata/src/main/AndroidManifest.xml b/lifecycle/livedata/src/main/AndroidManifest.xml
index 9f738ee..ecd0fe1 100644
--- a/lifecycle/livedata/src/main/AndroidManifest.xml
+++ b/lifecycle/livedata/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle.livedata">
+          package="androidx.lifecycle.livedata">
 </manifest>
diff --git a/lifecycle/livedata/src/main/java/android/arch/lifecycle/ComputableLiveData.java b/lifecycle/livedata/src/main/java/android/arch/lifecycle/ComputableLiveData.java
deleted file mode 100644
index d9e1ed5..0000000
--- a/lifecycle/livedata/src/main/java/android/arch/lifecycle/ComputableLiveData.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A LiveData class that can be invalidated & computed when there are active observers.
- * <p>
- * It can be invalidated via {@link #invalidate()}, which will result in a call to
- * {@link #compute()} if there are active observers (or when they start observing)
- * <p>
- * This is an internal class for now, might be public if we see the necessity.
- *
- * @param <T> The type of the live data
- * @hide internal
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class ComputableLiveData<T> {
-
-    private final Executor mExecutor;
-    private final LiveData<T> mLiveData;
-
-    private AtomicBoolean mInvalid = new AtomicBoolean(true);
-    private AtomicBoolean mComputing = new AtomicBoolean(false);
-
-    /**
-     * Creates a computable live data that computes values on the arch IO thread executor.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public ComputableLiveData() {
-        this(ArchTaskExecutor.getIOThreadExecutor());
-    }
-
-    /**
-     *
-     * Creates a computable live data that computes values on the specified executor.
-     *
-     * @param executor Executor that is used to compute new LiveData values.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public ComputableLiveData(@NonNull Executor executor) {
-        mExecutor = executor;
-        mLiveData = new LiveData<T>() {
-            @Override
-            protected void onActive() {
-                mExecutor.execute(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) {
-                    mExecutor.execute(mRefreshRunnable);
-                }
-            }
-        }
-    };
-
-    /**
-     * Invalidates the LiveData.
-     * <p>
-     * When there are active observers, this will trigger a call to {@link #compute()}.
-     */
-    public void invalidate() {
-        ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @WorkerThread
-    protected abstract T compute();
-}
diff --git a/lifecycle/livedata/src/main/java/android/arch/lifecycle/MediatorLiveData.java b/lifecycle/livedata/src/main/java/android/arch/lifecycle/MediatorLiveData.java
deleted file mode 100644
index 3960163..0000000
--- a/lifecycle/livedata/src/main/java/android/arch/lifecycle/MediatorLiveData.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.arch.core.internal.SafeIterableMap;
-import android.support.annotation.CallSuper;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.Map;
-
-/**
- * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
- * {@code OnChanged} events from them.
- * <p>
- * This class correctly propagates its active/inactive states down to source {@code LiveData}
- * objects.
- * <p>
- * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
- * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
- * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
- * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
- * is called for either of them, we set a new value in {@code liveDataMerger}.
- *
- * <pre>
- * LiveData<Integer> liveData1 = ...;
- * LiveData<Integer> liveData2 = ...;
- *
- * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
- * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
- * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
- * </pre>
- * <p>
- * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
- * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
- * liveData1} and remove it as a source.
- * <pre>
- * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
- *      private int count = 1;
- *
- *      {@literal @}Override public void onChanged(@Nullable Integer s) {
- *          count++;
- *          liveDataMerger.setValue(s);
- *          if (count > 10) {
- *              liveDataMerger.removeSource(liveData1);
- *          }
- *      }
- * });
- * </pre>
- *
- * @param <T> The type of data hold by this instance
- */
-@SuppressWarnings("WeakerAccess")
-public class MediatorLiveData<T> extends MutableLiveData<T> {
-    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();
-
-    /**
-     * Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called
-     * when {@code source} value was changed.
-     * <p>
-     * {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
-     * <p> If the given LiveData is already added as a source but with a different Observer,
-     * {@link IllegalArgumentException} will be thrown.
-     *
-     * @param source    the {@code LiveData} to listen to
-     * @param onChanged The observer that will receive the events
-     * @param <S>       The type of data hold by {@code source} LiveData
-     */
-    @MainThread
-    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
-        Source<S> e = new Source<>(source, onChanged);
-        Source<?> existing = mSources.putIfAbsent(source, e);
-        if (existing != null && existing.mObserver != onChanged) {
-            throw new IllegalArgumentException(
-                    "This source was already added with the different observer");
-        }
-        if (existing != null) {
-            return;
-        }
-        if (hasActiveObservers()) {
-            e.plug();
-        }
-    }
-
-    /**
-     * Stops to listen the given {@code LiveData}.
-     *
-     * @param toRemote {@code LiveData} to stop to listen
-     * @param <S>      the type of data hold by {@code source} LiveData
-     */
-    @MainThread
-    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
-        Source<?> source = mSources.remove(toRemote);
-        if (source != null) {
-            source.unplug();
-        }
-    }
-
-    @CallSuper
-    @Override
-    protected void onActive() {
-        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
-            source.getValue().plug();
-        }
-    }
-
-    @CallSuper
-    @Override
-    protected void onInactive() {
-        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
-            source.getValue().unplug();
-        }
-    }
-
-    private static class Source<V> implements Observer<V> {
-        final LiveData<V> mLiveData;
-        final Observer<? super V> mObserver;
-        int mVersion = START_VERSION;
-
-        Source(LiveData<V> liveData, final Observer<? super V> observer) {
-            mLiveData = liveData;
-            mObserver = observer;
-        }
-
-        void plug() {
-            mLiveData.observeForever(this);
-        }
-
-        void unplug() {
-            mLiveData.removeObserver(this);
-        }
-
-        @Override
-        public void onChanged(@Nullable V v) {
-            if (mVersion != mLiveData.getVersion()) {
-                mVersion = mLiveData.getVersion();
-                mObserver.onChanged(v);
-            }
-        }
-    }
-}
diff --git a/lifecycle/livedata/src/main/java/android/arch/lifecycle/Transformations.java b/lifecycle/livedata/src/main/java/android/arch/lifecycle/Transformations.java
deleted file mode 100644
index c735f8b..0000000
--- a/lifecycle/livedata/src/main/java/android/arch/lifecycle/Transformations.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.arch.core.util.Function;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-/**
- * 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 {
-
-    private Transformations() {
-    }
-
-    /**
-     * Applies the given function on the main thread to each value emitted by {@code source}
-     * 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
-     * @param <X>    a type of {@code source} LiveData
-     * @param <Y>    a type of resulting LiveData.
-     * @return a LiveData which emits resulting values
-     */
-    @MainThread
-    public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
-            @NonNull final Function<X, Y> func) {
-        final MediatorLiveData<Y> result = new MediatorLiveData<>();
-        result.addSource(source, new Observer<X>() {
-            @Override
-            public void onChanged(@Nullable X x) {
-                result.setValue(func.apply(x));
-            }
-        });
-        return result;
-    }
-
-    /**
-     * Creates a LiveData, let's name it {@code swLiveData}, which follows next flow:
-     * it reacts on changes of {@code trigger} LiveData, applies the given function to new value of
-     * {@code trigger} LiveData and sets resulting LiveData as a "backing" LiveData
-     * to {@code swLiveData}.
-     * "Backing" LiveData means, that all events emitted by it will retransmitted
-     * by {@code swLiveData}.
-     * <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
-     * @param <Y>     a type of resulting LiveData
-     */
-    @MainThread
-    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
-            @NonNull final Function<X, LiveData<Y>> func) {
-        final MediatorLiveData<Y> result = new MediatorLiveData<>();
-        result.addSource(trigger, new Observer<X>() {
-            LiveData<Y> mSource;
-
-            @Override
-            public void onChanged(@Nullable X x) {
-                LiveData<Y> newLiveData = func.apply(x);
-                if (mSource == newLiveData) {
-                    return;
-                }
-                if (mSource != null) {
-                    result.removeSource(mSource);
-                }
-                mSource = newLiveData;
-                if (mSource != null) {
-                    result.addSource(mSource, new Observer<Y>() {
-                        @Override
-                        public void onChanged(@Nullable Y y) {
-                            result.setValue(y);
-                        }
-                    });
-                }
-            }
-        });
-        return result;
-    }
-}
diff --git a/lifecycle/livedata/src/main/java/androidx/lifecycle/ComputableLiveData.java b/lifecycle/livedata/src/main/java/androidx/lifecycle/ComputableLiveData.java
new file mode 100644
index 0000000..e2e2a1b
--- /dev/null
+++ b/lifecycle/livedata/src/main/java/androidx/lifecycle/ComputableLiveData.java
@@ -0,0 +1,148 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.arch.core.executor.ArchTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A LiveData class that can be invalidated & computed when there are active observers.
+ * <p>
+ * It can be invalidated via {@link #invalidate()}, which will result in a call to
+ * {@link #compute()} if there are active observers (or when they start observing)
+ * <p>
+ * This is an internal class for now, might be public if we see the necessity.
+ *
+ * @param <T> The type of the live data
+ * @hide internal
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class ComputableLiveData<T> {
+
+    private final Executor mExecutor;
+    private final LiveData<T> mLiveData;
+
+    private AtomicBoolean mInvalid = new AtomicBoolean(true);
+    private AtomicBoolean mComputing = new AtomicBoolean(false);
+
+    /**
+     * Creates a computable live data that computes values on the arch IO thread executor.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public ComputableLiveData() {
+        this(ArchTaskExecutor.getIOThreadExecutor());
+    }
+
+    /**
+     *
+     * Creates a computable live data that computes values on the specified executor.
+     *
+     * @param executor Executor that is used to compute new LiveData values.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public ComputableLiveData(@NonNull Executor executor) {
+        mExecutor = executor;
+        mLiveData = new LiveData<T>() {
+            @Override
+            protected void onActive() {
+                mExecutor.execute(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) {
+                    mExecutor.execute(mRefreshRunnable);
+                }
+            }
+        }
+    };
+
+    /**
+     * Invalidates the LiveData.
+     * <p>
+     * When there are active observers, this will trigger a call to {@link #compute()}.
+     */
+    public void invalidate() {
+        ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @WorkerThread
+    protected abstract T compute();
+}
diff --git a/lifecycle/livedata/src/main/java/androidx/lifecycle/MediatorLiveData.java b/lifecycle/livedata/src/main/java/androidx/lifecycle/MediatorLiveData.java
new file mode 100644
index 0000000..58261dd
--- /dev/null
+++ b/lifecycle/livedata/src/main/java/androidx/lifecycle/MediatorLiveData.java
@@ -0,0 +1,156 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.internal.SafeIterableMap;
+
+import java.util.Map;
+
+/**
+ * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
+ * {@code OnChanged} events from them.
+ * <p>
+ * This class correctly propagates its active/inactive states down to source {@code LiveData}
+ * objects.
+ * <p>
+ * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
+ * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
+ * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
+ * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
+ * is called for either of them, we set a new value in {@code liveDataMerger}.
+ *
+ * <pre>
+ * LiveData<Integer> liveData1 = ...;
+ * LiveData<Integer> liveData2 = ...;
+ *
+ * MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
+ * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
+ * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
+ * </pre>
+ * <p>
+ * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
+ * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
+ * liveData1} and remove it as a source.
+ * <pre>
+ * liveDataMerger.addSource(liveData1, new Observer<Integer>() {
+ *      private int count = 1;
+ *
+ *      {@literal @}Override public void onChanged(@Nullable Integer s) {
+ *          count++;
+ *          liveDataMerger.setValue(s);
+ *          if (count > 10) {
+ *              liveDataMerger.removeSource(liveData1);
+ *          }
+ *      }
+ * });
+ * </pre>
+ *
+ * @param <T> The type of data hold by this instance
+ */
+@SuppressWarnings("WeakerAccess")
+public class MediatorLiveData<T> extends MutableLiveData<T> {
+    private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();
+
+    /**
+     * Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called
+     * when {@code source} value was changed.
+     * <p>
+     * {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
+     * <p> If the given LiveData is already added as a source but with a different Observer,
+     * {@link IllegalArgumentException} will be thrown.
+     *
+     * @param source    the {@code LiveData} to listen to
+     * @param onChanged The observer that will receive the events
+     * @param <S>       The type of data hold by {@code source} LiveData
+     */
+    @MainThread
+    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
+        Source<S> e = new Source<>(source, onChanged);
+        Source<?> existing = mSources.putIfAbsent(source, e);
+        if (existing != null && existing.mObserver != onChanged) {
+            throw new IllegalArgumentException(
+                    "This source was already added with the different observer");
+        }
+        if (existing != null) {
+            return;
+        }
+        if (hasActiveObservers()) {
+            e.plug();
+        }
+    }
+
+    /**
+     * Stops to listen the given {@code LiveData}.
+     *
+     * @param toRemote {@code LiveData} to stop to listen
+     * @param <S>      the type of data hold by {@code source} LiveData
+     */
+    @MainThread
+    public <S> void removeSource(@NonNull LiveData<S> toRemote) {
+        Source<?> source = mSources.remove(toRemote);
+        if (source != null) {
+            source.unplug();
+        }
+    }
+
+    @CallSuper
+    @Override
+    protected void onActive() {
+        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
+            source.getValue().plug();
+        }
+    }
+
+    @CallSuper
+    @Override
+    protected void onInactive() {
+        for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
+            source.getValue().unplug();
+        }
+    }
+
+    private static class Source<V> implements Observer<V> {
+        final LiveData<V> mLiveData;
+        final Observer<? super V> mObserver;
+        int mVersion = START_VERSION;
+
+        Source(LiveData<V> liveData, final Observer<? super V> observer) {
+            mLiveData = liveData;
+            mObserver = observer;
+        }
+
+        void plug() {
+            mLiveData.observeForever(this);
+        }
+
+        void unplug() {
+            mLiveData.removeObserver(this);
+        }
+
+        @Override
+        public void onChanged(@Nullable V v) {
+            if (mVersion != mLiveData.getVersion()) {
+                mVersion = mLiveData.getVersion();
+                mObserver.onChanged(v);
+            }
+        }
+    }
+}
diff --git a/lifecycle/livedata/src/main/java/androidx/lifecycle/Transformations.java b/lifecycle/livedata/src/main/java/androidx/lifecycle/Transformations.java
new file mode 100644
index 0000000..392c3de
--- /dev/null
+++ b/lifecycle/livedata/src/main/java/androidx/lifecycle/Transformations.java
@@ -0,0 +1,153 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Function;
+
+/**
+ * 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 {
+
+    private Transformations() {
+    }
+
+    /**
+     * Applies the given function on the main thread to each value emitted by {@code source}
+     * 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
+     * @param <X>    a type of {@code source} LiveData
+     * @param <Y>    a type of resulting LiveData.
+     * @return a LiveData which emits resulting values
+     */
+    @MainThread
+    public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
+            @NonNull final Function<X, Y> func) {
+        final MediatorLiveData<Y> result = new MediatorLiveData<>();
+        result.addSource(source, new Observer<X>() {
+            @Override
+            public void onChanged(@Nullable X x) {
+                result.setValue(func.apply(x));
+            }
+        });
+        return result;
+    }
+
+    /**
+     * Creates a LiveData, let's name it {@code swLiveData}, which follows next flow:
+     * it reacts on changes of {@code trigger} LiveData, applies the given function to new value of
+     * {@code trigger} LiveData and sets resulting LiveData as a "backing" LiveData
+     * to {@code swLiveData}.
+     * "Backing" LiveData means, that all events emitted by it will retransmitted
+     * by {@code swLiveData}.
+     * <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
+     * @param <Y>     a type of resulting LiveData
+     */
+    @MainThread
+    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
+            @NonNull final Function<X, LiveData<Y>> func) {
+        final MediatorLiveData<Y> result = new MediatorLiveData<>();
+        result.addSource(trigger, new Observer<X>() {
+            LiveData<Y> mSource;
+
+            @Override
+            public void onChanged(@Nullable X x) {
+                LiveData<Y> newLiveData = func.apply(x);
+                if (mSource == newLiveData) {
+                    return;
+                }
+                if (mSource != null) {
+                    result.removeSource(mSource);
+                }
+                mSource = newLiveData;
+                if (mSource != null) {
+                    result.addSource(mSource, new Observer<Y>() {
+                        @Override
+                        public void onChanged(@Nullable Y y) {
+                            result.setValue(y);
+                        }
+                    });
+                }
+            }
+        });
+        return result;
+    }
+}
diff --git a/lifecycle/livedata/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java
deleted file mode 100644
index 9130b25..0000000
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/ComputableLiveDataTest.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
-import android.arch.core.executor.TaskExecutorWithFakeMainThread;
-import android.arch.lifecycle.util.InstantTaskExecutor;
-import android.support.annotation.NonNull;
-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.ArgumentCaptor;
-
-import java.util.Collections;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@RunWith(JUnit4.class)
-public class ComputableLiveDataTest {
-    private TaskExecutor mTaskExecutor;
-    private TestLifecycleOwner mLifecycleOwner;
-
-    @Before
-    public void setup() {
-        mLifecycleOwner = new TestLifecycleOwner();
-    }
-
-    @Before
-    public void swapExecutorDelegate() {
-        mTaskExecutor = spy(new InstantTaskExecutor());
-        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
-    }
-
-    @After
-    public void removeExecutorDelegate() {
-        ArchTaskExecutor.getInstance().setDelegate(null);
-    }
-
-    @Test
-    public void noComputeWithoutObservers() {
-        final TestComputable computable = new TestComputable();
-        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
-        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mInvalidationRunnable);
-    }
-
-    @Test
-    public void noConcurrentCompute() throws InterruptedException {
-        TaskExecutorWithFakeMainThread executor = new TaskExecutorWithFakeMainThread(2);
-        ArchTaskExecutor.getInstance().setDelegate(executor);
-        try {
-            // # of compute calls
-            final Semaphore computeCounter = new Semaphore(0);
-            // available permits for computation
-            final Semaphore computeLock = new Semaphore(0);
-            final TestComputable computable = new TestComputable(1, 2) {
-                @Override
-                protected Integer compute() {
-                    try {
-                        computeCounter.release(1);
-                        computeLock.tryAcquire(1, 20, TimeUnit.SECONDS);
-                    } catch (InterruptedException e) {
-                        throw new AssertionError(e);
-                    }
-                    return super.compute();
-                }
-            };
-            final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
-            //noinspection unchecked
-            final Observer<Integer> observer = mock(Observer.class);
-            executor.postToMainThread(new Runnable() {
-                @Override
-                public void run() {
-                    computable.getLiveData().observeForever(observer);
-                    verify(observer, never()).onChanged(anyInt());
-                }
-            });
-            // wait for first compute call
-            assertThat(computeCounter.tryAcquire(1, 2, TimeUnit.SECONDS), is(true));
-            // re-invalidate while in compute
-            computable.invalidate();
-            computable.invalidate();
-            computable.invalidate();
-            computable.invalidate();
-            // ensure another compute call does not arrive
-            assertThat(computeCounter.tryAcquire(1, 2, TimeUnit.SECONDS), is(false));
-            // allow computation to finish
-            computeLock.release(2);
-            // wait for the second result, first will be skipped due to invalidation during compute
-            verify(observer, timeout(2000)).onChanged(captor.capture());
-            assertThat(captor.getAllValues(), is(Collections.singletonList(2)));
-            reset(observer);
-            // allow all computations to run, there should not be any.
-            computeLock.release(100);
-            // unfortunately, Mockito.after is not available in 1.9.5
-            executor.drainTasks(2);
-            // assert no other results arrive
-            verify(observer, never()).onChanged(anyInt());
-        } finally {
-            ArchTaskExecutor.getInstance().setDelegate(null);
-        }
-    }
-
-    @Test
-    public void addingObserverShouldTriggerAComputation() {
-        TestComputable computable = new TestComputable(1);
-        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_CREATE);
-        final AtomicInteger mValue = new AtomicInteger(-1);
-        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
-            @Override
-            public void onChanged(@Nullable Integer integer) {
-                //noinspection ConstantConditions
-                mValue.set(integer);
-            }
-        });
-        verify(mTaskExecutor, never()).executeOnDiskIO(any(Runnable.class));
-        assertThat(mValue.get(), is(-1));
-        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        verify(mTaskExecutor).executeOnDiskIO(computable.mRefreshRunnable);
-        assertThat(mValue.get(), is(1));
-    }
-
-    @Test
-    public void customExecutor() {
-        Executor customExecutor = mock(Executor.class);
-        TestComputable computable = new TestComputable(customExecutor, 1);
-        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_CREATE);
-        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
-            @Override
-            public void onChanged(@Nullable Integer integer) {
-                // ignored
-            }
-        });
-        verify(mTaskExecutor, never()).executeOnDiskIO(any(Runnable.class));
-        verify(customExecutor, never()).execute(any(Runnable.class));
-
-        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-
-        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
-        verify(customExecutor).execute(computable.mRefreshRunnable);
-    }
-
-    @Test
-    public void invalidationShouldNotReTriggerComputationIfObserverIsInActive() {
-        TestComputable computable = new TestComputable(1, 2);
-        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        final AtomicInteger mValue = new AtomicInteger(-1);
-        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
-            @Override
-            public void onChanged(@Nullable Integer integer) {
-                //noinspection ConstantConditions
-                mValue.set(integer);
-            }
-        });
-        assertThat(mValue.get(), is(1));
-        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP);
-        computable.invalidate();
-        reset(mTaskExecutor);
-        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
-        assertThat(mValue.get(), is(1));
-    }
-
-    @Test
-    public void invalidationShouldReTriggerQueryIfObserverIsActive() {
-        TestComputable computable = new TestComputable(1, 2);
-        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        final AtomicInteger mValue = new AtomicInteger(-1);
-        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
-            @Override
-            public void onChanged(@Nullable Integer integer) {
-                //noinspection ConstantConditions
-                mValue.set(integer);
-            }
-        });
-        assertThat(mValue.get(), is(1));
-        computable.invalidate();
-        assertThat(mValue.get(), is(2));
-    }
-
-    static class TestComputable extends ComputableLiveData<Integer> {
-        final int[] mValues;
-        AtomicInteger mValueCounter = new AtomicInteger();
-
-        TestComputable(@NonNull Executor executor, int... values) {
-            super(executor);
-            mValues = values;
-        }
-
-        TestComputable(int... values) {
-            mValues = values;
-        }
-
-        @Override
-        protected Integer compute() {
-            return mValues[mValueCounter.getAndIncrement()];
-        }
-    }
-
-    static class TestLifecycleOwner implements LifecycleOwner {
-        private LifecycleRegistry mLifecycle;
-
-        TestLifecycleOwner() {
-            mLifecycle = new LifecycleRegistry(this);
-        }
-
-        @Override
-        public Lifecycle getLifecycle() {
-            return mLifecycle;
-        }
-
-        void handleEvent(Lifecycle.Event event) {
-            mLifecycle.handleLifecycleEvent(event);
-        }
-    }
-}
diff --git a/lifecycle/livedata/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java
deleted file mode 100644
index 9dbeb44..0000000
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/MediatorLiveDataTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.testing.InstantTaskExecutorRule;
-import android.arch.lifecycle.util.InstantTaskExecutor;
-import android.support.annotation.Nullable;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SuppressWarnings("unchecked")
-@RunWith(JUnit4.class)
-public class MediatorLiveDataTest {
-
-    @Rule
-    public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
-
-    private LifecycleOwner mOwner;
-    private LifecycleRegistry mRegistry;
-    private MediatorLiveData<String> mMediator;
-    private LiveData<String> mSource;
-    private boolean mSourceActive;
-
-    @Before
-    public void setup() {
-        mOwner = mock(LifecycleOwner.class);
-        mRegistry = new LifecycleRegistry(mOwner);
-        when(mOwner.getLifecycle()).thenReturn(mRegistry);
-        mMediator = new MediatorLiveData<>();
-        mSource = new LiveData<String>() {
-            @Override
-            protected void onActive() {
-                mSourceActive = true;
-            }
-
-            @Override
-            protected void onInactive() {
-                mSourceActive = false;
-            }
-        };
-        mSourceActive = false;
-        mMediator.observe(mOwner, mock(Observer.class));
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-    }
-
-    @Before
-    public void swapExecutorDelegate() {
-        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
-    }
-
-    @Test
-    public void testSingleDelivery() {
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        mSource.setValue("flatfoot");
-        verify(observer).onChanged("flatfoot");
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        reset(observer);
-        verify(observer, never()).onChanged(any());
-    }
-
-    @Test
-    public void testChangeWhileInactive() {
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        mMediator.observe(mOwner, mock(Observer.class));
-        mSource.setValue("one");
-        verify(observer).onChanged("one");
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        reset(observer);
-        mSource.setValue("flatfoot");
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        verify(observer).onChanged("flatfoot");
-    }
-
-
-    @Test
-    public void testAddSourceToActive() {
-        mSource.setValue("flatfoot");
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        verify(observer).onChanged("flatfoot");
-    }
-
-    @Test
-    public void testAddSourceToInActive() {
-        mSource.setValue("flatfoot");
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        verify(observer, never()).onChanged(any());
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        verify(observer).onChanged("flatfoot");
-    }
-
-    @Test
-    public void testRemoveSource() {
-        mSource.setValue("flatfoot");
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        verify(observer).onChanged("flatfoot");
-        mMediator.removeSource(mSource);
-        reset(observer);
-        mSource.setValue("failure");
-        verify(observer, never()).onChanged(any());
-    }
-
-    @Test
-    public void testSourceInactive() {
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        assertThat(mSourceActive, is(true));
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        assertThat(mSourceActive, is(false));
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        assertThat(mSourceActive, is(true));
-    }
-
-    @Test
-    public void testNoLeakObserver() {
-        // Imitates a destruction of a ViewModel: a listener of LiveData is destroyed,
-        // a reference to MediatorLiveData is cleaned up. In this case we shouldn't leak
-        // MediatorLiveData as an observer of mSource.
-        assertThat(mSource.hasObservers(), is(false));
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        assertThat(mSource.hasObservers(), is(true));
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
-        mMediator = null;
-        assertThat(mSource.hasObservers(), is(false));
-    }
-
-    @Test
-    public void testMultipleSources() {
-        Observer observer1 = mock(Observer.class);
-        mMediator.addSource(mSource, observer1);
-        MutableLiveData<Integer> source2 = new MutableLiveData<>();
-        Observer observer2 = mock(Observer.class);
-        mMediator.addSource(source2, observer2);
-        mSource.setValue("flatfoot");
-        verify(observer1).onChanged("flatfoot");
-        verify(observer2, never()).onChanged(any());
-        reset(observer1, observer2);
-        source2.setValue(1703);
-        verify(observer1, never()).onChanged(any());
-        verify(observer2).onChanged(1703);
-        reset(observer1, observer2);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        mSource.setValue("failure");
-        source2.setValue(0);
-        verify(observer1, never()).onChanged(any());
-        verify(observer2, never()).onChanged(any());
-    }
-
-    @Test
-    public void removeSourceDuringOnActive() {
-        // to trigger ConcurrentModificationException,
-        // we have to call remove from a collection during "for" loop.
-        // ConcurrentModificationException is thrown from next() method of an iterator
-        // so this modification shouldn't be at the last iteration,
-        // because if it is a last iteration, then next() wouldn't be called.
-        // And the last: an order of an iteration over sources is not defined,
-        // so I have to call it remove operation  from all observers.
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        Observer<String> removingObserver = new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                mMediator.removeSource(mSource);
-            }
-        };
-        mMediator.addSource(mSource, removingObserver);
-        MutableLiveData<String> source2 = new MutableLiveData<>();
-        source2.setValue("nana");
-        mMediator.addSource(source2, removingObserver);
-        mSource.setValue("petjack");
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void reAddSameSourceWithDifferentObserver() {
-        mMediator.addSource(mSource, mock(Observer.class));
-        mMediator.addSource(mSource, mock(Observer.class));
-    }
-
-    @Test
-    public void addSameSourceWithSameObserver() {
-        Observer observer = mock(Observer.class);
-        mMediator.addSource(mSource, observer);
-        mMediator.addSource(mSource, observer);
-        // no exception was thrown
-    }
-
-    @Test
-    public void addSourceDuringOnActive() {
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-        mSource.setValue("a");
-        mMediator.addSource(mSource, new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                MutableLiveData<String> source = new MutableLiveData<>();
-                source.setValue("b");
-                mMediator.addSource(source, new Observer<String>() {
-                    @Override
-                    public void onChanged(@Nullable String s) {
-                        mMediator.setValue("c");
-                    }
-                });
-            }
-        });
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        assertThat(mMediator.getValue(), is("c"));
-    }
-
-}
diff --git a/lifecycle/livedata/src/test/java/android/arch/lifecycle/TransformationsTest.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/TransformationsTest.java
deleted file mode 100644
index 02397da..0000000
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/TransformationsTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.util.Function;
-import android.arch.lifecycle.util.InstantTaskExecutor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SuppressWarnings("unchecked")
-@RunWith(JUnit4.class)
-public class TransformationsTest {
-
-    private LifecycleOwner mOwner;
-
-    @Before
-    public void swapExecutorDelegate() {
-        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
-    }
-
-    @Before
-    public void setup() {
-        mOwner = mock(LifecycleOwner.class);
-        LifecycleRegistry registry = new LifecycleRegistry(mOwner);
-        when(mOwner.getLifecycle()).thenReturn(registry);
-        registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
-        registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-    }
-
-    @Test
-    public void testMap() {
-        LiveData<String> source = new MutableLiveData<>();
-        LiveData<Integer> mapped = Transformations.map(source, new Function<String, Integer>() {
-            @Override
-            public Integer apply(String input) {
-                return input.length();
-            }
-        });
-        Observer<Integer> observer = mock(Observer.class);
-        mapped.observe(mOwner, observer);
-        source.setValue("four");
-        verify(observer).onChanged(4);
-    }
-
-    @Test
-    public void testSwitchMap() {
-        LiveData<Integer> trigger = new MutableLiveData<>();
-        final LiveData<String> first = new MutableLiveData<>();
-        final LiveData<String> second = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        if (input == 1) {
-                            return first;
-                        } else {
-                            return second;
-                        }
-                    }
-                });
-
-        Observer<String> observer = mock(Observer.class);
-        result.observe(mOwner, observer);
-        verify(observer, never()).onChanged(anyString());
-        first.setValue("first");
-        trigger.setValue(1);
-        verify(observer).onChanged("first");
-        second.setValue("second");
-        reset(observer);
-        verify(observer, never()).onChanged(anyString());
-        trigger.setValue(2);
-        verify(observer).onChanged("second");
-        reset(observer);
-        first.setValue("failure");
-        verify(observer, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void testSwitchMap2() {
-        LiveData<Integer> trigger = new MutableLiveData<>();
-        final LiveData<String> first = new MutableLiveData<>();
-        final LiveData<String> second = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        if (input == 1) {
-                            return first;
-                        } else {
-                            return second;
-                        }
-                    }
-                });
-
-        Observer<String> observer = mock(Observer.class);
-        result.observe(mOwner, observer);
-
-        verify(observer, never()).onChanged(anyString());
-        trigger.setValue(1);
-        verify(observer, never()).onChanged(anyString());
-        first.setValue("fi");
-        verify(observer).onChanged("fi");
-        first.setValue("rst");
-        verify(observer).onChanged("rst");
-
-        second.setValue("second");
-        reset(observer);
-        verify(observer, never()).onChanged(anyString());
-        trigger.setValue(2);
-        verify(observer).onChanged("second");
-        reset(observer);
-        first.setValue("failure");
-        verify(observer, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void testNoRedispatchSwitchMap() {
-        LiveData<Integer> trigger = new MutableLiveData<>();
-        final LiveData<String> first = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        return first;
-                    }
-                });
-
-        Observer<String> observer = mock(Observer.class);
-        result.observe(mOwner, observer);
-        verify(observer, never()).onChanged(anyString());
-        first.setValue("first");
-        trigger.setValue(1);
-        verify(observer).onChanged("first");
-        reset(observer);
-        trigger.setValue(2);
-        verify(observer, never()).onChanged(anyString());
-    }
-
-    @Test
-    public void testSwitchMapToNull() {
-        LiveData<Integer> trigger = new MutableLiveData<>();
-        final LiveData<String> first = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        if (input == 1) {
-                            return first;
-                        } else {
-                            return null;
-                        }
-                    }
-                });
-
-        Observer<String> observer = mock(Observer.class);
-        result.observe(mOwner, observer);
-        verify(observer, never()).onChanged(anyString());
-        first.setValue("first");
-        trigger.setValue(1);
-        verify(observer).onChanged("first");
-        reset(observer);
-
-        trigger.setValue(2);
-        verify(observer, never()).onChanged(anyString());
-        assertThat(first.hasObservers(), is(false));
-    }
-
-    @Test
-    public void noObsoleteValueTest() {
-        MutableLiveData<Integer> numbers = new MutableLiveData<>();
-        LiveData<Integer> squared = Transformations.map(numbers, new Function<Integer, Integer>() {
-            @Override
-            public Integer apply(Integer input) {
-                return input * input;
-            }
-        });
-
-        Observer observer = mock(Observer.class);
-        squared.setValue(1);
-        squared.observeForever(observer);
-        verify(observer).onChanged(1);
-        squared.removeObserver(observer);
-        reset(observer);
-        numbers.setValue(2);
-        squared.observeForever(observer);
-        verify(observer, only()).onChanged(4);
-    }
-}
diff --git a/lifecycle/livedata/src/test/java/android/arch/lifecycle/util/InstantTaskExecutor.java b/lifecycle/livedata/src/test/java/android/arch/lifecycle/util/InstantTaskExecutor.java
deleted file mode 100644
index 52318fd..0000000
--- a/lifecycle/livedata/src/test/java/android/arch/lifecycle/util/InstantTaskExecutor.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle.util;
-
-import android.arch.core.executor.TaskExecutor;
-
-public class InstantTaskExecutor extends TaskExecutor {
-    @Override
-    public void executeOnDiskIO(Runnable runnable) {
-        runnable.run();
-    }
-
-    @Override
-    public void postToMainThread(Runnable runnable) {
-        runnable.run();
-    }
-
-    @Override
-    public boolean isMainThread() {
-        return true;
-    }
-}
diff --git a/lifecycle/livedata/src/test/java/androidx/lifecycle/ComputableLiveDataTest.java b/lifecycle/livedata/src/test/java/androidx/lifecycle/ComputableLiveDataTest.java
new file mode 100644
index 0000000..2b9d37e
--- /dev/null
+++ b/lifecycle/livedata/src/test/java/androidx/lifecycle/ComputableLiveDataTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
+import androidx.arch.core.executor.TaskExecutorWithFakeMainThread;
+import androidx.lifecycle.util.InstantTaskExecutor;
+
+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.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RunWith(JUnit4.class)
+public class ComputableLiveDataTest {
+    private TaskExecutor mTaskExecutor;
+    private TestLifecycleOwner mLifecycleOwner;
+
+    @Before
+    public void setup() {
+        mLifecycleOwner = new TestLifecycleOwner();
+    }
+
+    @Before
+    public void swapExecutorDelegate() {
+        mTaskExecutor = spy(new InstantTaskExecutor());
+        ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+    }
+
+    @After
+    public void removeExecutorDelegate() {
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    @Test
+    public void noComputeWithoutObservers() {
+        final TestComputable computable = new TestComputable();
+        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
+        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mInvalidationRunnable);
+    }
+
+    @Test
+    public void noConcurrentCompute() throws InterruptedException {
+        TaskExecutorWithFakeMainThread executor = new TaskExecutorWithFakeMainThread(2);
+        ArchTaskExecutor.getInstance().setDelegate(executor);
+        try {
+            // # of compute calls
+            final Semaphore computeCounter = new Semaphore(0);
+            // available permits for computation
+            final Semaphore computeLock = new Semaphore(0);
+            final TestComputable computable = new TestComputable(1, 2) {
+                @Override
+                protected Integer compute() {
+                    try {
+                        computeCounter.release(1);
+                        computeLock.tryAcquire(1, 20, TimeUnit.SECONDS);
+                    } catch (InterruptedException e) {
+                        throw new AssertionError(e);
+                    }
+                    return super.compute();
+                }
+            };
+            final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
+            //noinspection unchecked
+            final Observer<Integer> observer = mock(Observer.class);
+            executor.postToMainThread(new Runnable() {
+                @Override
+                public void run() {
+                    computable.getLiveData().observeForever(observer);
+                    verify(observer, never()).onChanged(anyInt());
+                }
+            });
+            // wait for first compute call
+            assertThat(computeCounter.tryAcquire(1, 2, TimeUnit.SECONDS), is(true));
+            // re-invalidate while in compute
+            computable.invalidate();
+            computable.invalidate();
+            computable.invalidate();
+            computable.invalidate();
+            // ensure another compute call does not arrive
+            assertThat(computeCounter.tryAcquire(1, 2, TimeUnit.SECONDS), is(false));
+            // allow computation to finish
+            computeLock.release(2);
+            // wait for the second result, first will be skipped due to invalidation during compute
+            verify(observer, timeout(2000)).onChanged(captor.capture());
+            assertThat(captor.getAllValues(), is(Collections.singletonList(2)));
+            reset(observer);
+            // allow all computations to run, there should not be any.
+            computeLock.release(100);
+            // unfortunately, Mockito.after is not available in 1.9.5
+            executor.drainTasks(2);
+            // assert no other results arrive
+            verify(observer, never()).onChanged(anyInt());
+        } finally {
+            ArchTaskExecutor.getInstance().setDelegate(null);
+        }
+    }
+
+    @Test
+    public void addingObserverShouldTriggerAComputation() {
+        TestComputable computable = new TestComputable(1);
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_CREATE);
+        final AtomicInteger mValue = new AtomicInteger(-1);
+        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                //noinspection ConstantConditions
+                mValue.set(integer);
+            }
+        });
+        verify(mTaskExecutor, never()).executeOnDiskIO(any(Runnable.class));
+        assertThat(mValue.get(), is(-1));
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        verify(mTaskExecutor).executeOnDiskIO(computable.mRefreshRunnable);
+        assertThat(mValue.get(), is(1));
+    }
+
+    @Test
+    public void customExecutor() {
+        Executor customExecutor = mock(Executor.class);
+        TestComputable computable = new TestComputable(customExecutor, 1);
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_CREATE);
+        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                // ignored
+            }
+        });
+        verify(mTaskExecutor, never()).executeOnDiskIO(any(Runnable.class));
+        verify(customExecutor, never()).execute(any(Runnable.class));
+
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+
+        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
+        verify(customExecutor).execute(computable.mRefreshRunnable);
+    }
+
+    @Test
+    public void invalidationShouldNotReTriggerComputationIfObserverIsInActive() {
+        TestComputable computable = new TestComputable(1, 2);
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final AtomicInteger mValue = new AtomicInteger(-1);
+        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                //noinspection ConstantConditions
+                mValue.set(integer);
+            }
+        });
+        assertThat(mValue.get(), is(1));
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP);
+        computable.invalidate();
+        reset(mTaskExecutor);
+        verify(mTaskExecutor, never()).executeOnDiskIO(computable.mRefreshRunnable);
+        assertThat(mValue.get(), is(1));
+    }
+
+    @Test
+    public void invalidationShouldReTriggerQueryIfObserverIsActive() {
+        TestComputable computable = new TestComputable(1, 2);
+        mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final AtomicInteger mValue = new AtomicInteger(-1);
+        computable.getLiveData().observe(mLifecycleOwner, new Observer<Integer>() {
+            @Override
+            public void onChanged(@Nullable Integer integer) {
+                //noinspection ConstantConditions
+                mValue.set(integer);
+            }
+        });
+        assertThat(mValue.get(), is(1));
+        computable.invalidate();
+        assertThat(mValue.get(), is(2));
+    }
+
+    static class TestComputable extends ComputableLiveData<Integer> {
+        final int[] mValues;
+        AtomicInteger mValueCounter = new AtomicInteger();
+
+        TestComputable(@NonNull Executor executor, int... values) {
+            super(executor);
+            mValues = values;
+        }
+
+        TestComputable(int... values) {
+            mValues = values;
+        }
+
+        @Override
+        protected Integer compute() {
+            return mValues[mValueCounter.getAndIncrement()];
+        }
+    }
+
+    static class TestLifecycleOwner implements LifecycleOwner {
+        private LifecycleRegistry mLifecycle;
+
+        TestLifecycleOwner() {
+            mLifecycle = new LifecycleRegistry(this);
+        }
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return mLifecycle;
+        }
+
+        void handleEvent(Lifecycle.Event event) {
+            mLifecycle.handleLifecycleEvent(event);
+        }
+    }
+}
diff --git a/lifecycle/livedata/src/test/java/androidx/lifecycle/MediatorLiveDataTest.java b/lifecycle/livedata/src/test/java/androidx/lifecycle/MediatorLiveDataTest.java
new file mode 100644
index 0000000..a90c343
--- /dev/null
+++ b/lifecycle/livedata/src/test/java/androidx/lifecycle/MediatorLiveDataTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
+import androidx.lifecycle.util.InstantTaskExecutor;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SuppressWarnings("unchecked")
+@RunWith(JUnit4.class)
+public class MediatorLiveDataTest {
+
+    @Rule
+    public InstantTaskExecutorRule mInstantTaskExecutorRule = new InstantTaskExecutorRule();
+
+    private LifecycleOwner mOwner;
+    private LifecycleRegistry mRegistry;
+    private MediatorLiveData<String> mMediator;
+    private LiveData<String> mSource;
+    private boolean mSourceActive;
+
+    @Before
+    public void setup() {
+        mOwner = mock(LifecycleOwner.class);
+        mRegistry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(mRegistry);
+        mMediator = new MediatorLiveData<>();
+        mSource = new LiveData<String>() {
+            @Override
+            protected void onActive() {
+                mSourceActive = true;
+            }
+
+            @Override
+            protected void onInactive() {
+                mSourceActive = false;
+            }
+        };
+        mSourceActive = false;
+        mMediator.observe(mOwner, mock(Observer.class));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
+    @Before
+    public void swapExecutorDelegate() {
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+    }
+
+    @Test
+    public void testSingleDelivery() {
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        mSource.setValue("flatfoot");
+        verify(observer).onChanged("flatfoot");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        reset(observer);
+        verify(observer, never()).onChanged(any());
+    }
+
+    @Test
+    public void testChangeWhileInactive() {
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        mMediator.observe(mOwner, mock(Observer.class));
+        mSource.setValue("one");
+        verify(observer).onChanged("one");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        reset(observer);
+        mSource.setValue("flatfoot");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        verify(observer).onChanged("flatfoot");
+    }
+
+
+    @Test
+    public void testAddSourceToActive() {
+        mSource.setValue("flatfoot");
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        verify(observer).onChanged("flatfoot");
+    }
+
+    @Test
+    public void testAddSourceToInActive() {
+        mSource.setValue("flatfoot");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        verify(observer, never()).onChanged(any());
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        verify(observer).onChanged("flatfoot");
+    }
+
+    @Test
+    public void testRemoveSource() {
+        mSource.setValue("flatfoot");
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        verify(observer).onChanged("flatfoot");
+        mMediator.removeSource(mSource);
+        reset(observer);
+        mSource.setValue("failure");
+        verify(observer, never()).onChanged(any());
+    }
+
+    @Test
+    public void testSourceInactive() {
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        assertThat(mSourceActive, is(true));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        assertThat(mSourceActive, is(false));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(mSourceActive, is(true));
+    }
+
+    @Test
+    public void testNoLeakObserver() {
+        // Imitates a destruction of a ViewModel: a listener of LiveData is destroyed,
+        // a reference to MediatorLiveData is cleaned up. In this case we shouldn't leak
+        // MediatorLiveData as an observer of mSource.
+        assertThat(mSource.hasObservers(), is(false));
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        assertThat(mSource.hasObservers(), is(true));
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+        mMediator = null;
+        assertThat(mSource.hasObservers(), is(false));
+    }
+
+    @Test
+    public void testMultipleSources() {
+        Observer observer1 = mock(Observer.class);
+        mMediator.addSource(mSource, observer1);
+        MutableLiveData<Integer> source2 = new MutableLiveData<>();
+        Observer observer2 = mock(Observer.class);
+        mMediator.addSource(source2, observer2);
+        mSource.setValue("flatfoot");
+        verify(observer1).onChanged("flatfoot");
+        verify(observer2, never()).onChanged(any());
+        reset(observer1, observer2);
+        source2.setValue(1703);
+        verify(observer1, never()).onChanged(any());
+        verify(observer2).onChanged(1703);
+        reset(observer1, observer2);
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mSource.setValue("failure");
+        source2.setValue(0);
+        verify(observer1, never()).onChanged(any());
+        verify(observer2, never()).onChanged(any());
+    }
+
+    @Test
+    public void removeSourceDuringOnActive() {
+        // to trigger ConcurrentModificationException,
+        // we have to call remove from a collection during "for" loop.
+        // ConcurrentModificationException is thrown from next() method of an iterator
+        // so this modification shouldn't be at the last iteration,
+        // because if it is a last iteration, then next() wouldn't be called.
+        // And the last: an order of an iteration over sources is not defined,
+        // so I have to call it remove operation  from all observers.
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        Observer<String> removingObserver = new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                mMediator.removeSource(mSource);
+            }
+        };
+        mMediator.addSource(mSource, removingObserver);
+        MutableLiveData<String> source2 = new MutableLiveData<>();
+        source2.setValue("nana");
+        mMediator.addSource(source2, removingObserver);
+        mSource.setValue("petjack");
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void reAddSameSourceWithDifferentObserver() {
+        mMediator.addSource(mSource, mock(Observer.class));
+        mMediator.addSource(mSource, mock(Observer.class));
+    }
+
+    @Test
+    public void addSameSourceWithSameObserver() {
+        Observer observer = mock(Observer.class);
+        mMediator.addSource(mSource, observer);
+        mMediator.addSource(mSource, observer);
+        // no exception was thrown
+    }
+
+    @Test
+    public void addSourceDuringOnActive() {
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+        mSource.setValue("a");
+        mMediator.addSource(mSource, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                MutableLiveData<String> source = new MutableLiveData<>();
+                source.setValue("b");
+                mMediator.addSource(source, new Observer<String>() {
+                    @Override
+                    public void onChanged(@Nullable String s) {
+                        mMediator.setValue("c");
+                    }
+                });
+            }
+        });
+        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        assertThat(mMediator.getValue(), is("c"));
+    }
+
+}
diff --git a/lifecycle/livedata/src/test/java/androidx/lifecycle/TransformationsTest.java b/lifecycle/livedata/src/test/java/androidx/lifecycle/TransformationsTest.java
new file mode 100644
index 0000000..4994523
--- /dev/null
+++ b/lifecycle/livedata/src/test/java/androidx/lifecycle/TransformationsTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.arch.core.util.Function;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.lifecycle.util.InstantTaskExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SuppressWarnings("unchecked")
+@RunWith(JUnit4.class)
+public class TransformationsTest {
+
+    private LifecycleOwner mOwner;
+
+    @Before
+    public void swapExecutorDelegate() {
+        ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+    }
+
+    @Before
+    public void setup() {
+        mOwner = mock(LifecycleOwner.class);
+        LifecycleRegistry registry = new LifecycleRegistry(mOwner);
+        when(mOwner.getLifecycle()).thenReturn(registry);
+        registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+    }
+
+    @Test
+    public void testMap() {
+        LiveData<String> source = new MutableLiveData<>();
+        LiveData<Integer> mapped = Transformations.map(source, new Function<String, Integer>() {
+            @Override
+            public Integer apply(String input) {
+                return input.length();
+            }
+        });
+        Observer<Integer> observer = mock(Observer.class);
+        mapped.observe(mOwner, observer);
+        source.setValue("four");
+        verify(observer).onChanged(4);
+    }
+
+    @Test
+    public void testSwitchMap() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        final LiveData<String> second = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        if (input == 1) {
+                            return first;
+                        } else {
+                            return second;
+                        }
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("first");
+        trigger.setValue(1);
+        verify(observer).onChanged("first");
+        second.setValue("second");
+        reset(observer);
+        verify(observer, never()).onChanged(anyString());
+        trigger.setValue(2);
+        verify(observer).onChanged("second");
+        reset(observer);
+        first.setValue("failure");
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testSwitchMap2() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        final LiveData<String> second = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        if (input == 1) {
+                            return first;
+                        } else {
+                            return second;
+                        }
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+
+        verify(observer, never()).onChanged(anyString());
+        trigger.setValue(1);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("fi");
+        verify(observer).onChanged("fi");
+        first.setValue("rst");
+        verify(observer).onChanged("rst");
+
+        second.setValue("second");
+        reset(observer);
+        verify(observer, never()).onChanged(anyString());
+        trigger.setValue(2);
+        verify(observer).onChanged("second");
+        reset(observer);
+        first.setValue("failure");
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testNoRedispatchSwitchMap() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        return first;
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("first");
+        trigger.setValue(1);
+        verify(observer).onChanged("first");
+        reset(observer);
+        trigger.setValue(2);
+        verify(observer, never()).onChanged(anyString());
+    }
+
+    @Test
+    public void testSwitchMapToNull() {
+        LiveData<Integer> trigger = new MutableLiveData<>();
+        final LiveData<String> first = new MutableLiveData<>();
+        LiveData<String> result = Transformations.switchMap(trigger,
+                new Function<Integer, LiveData<String>>() {
+                    @Override
+                    public LiveData<String> apply(Integer input) {
+                        if (input == 1) {
+                            return first;
+                        } else {
+                            return null;
+                        }
+                    }
+                });
+
+        Observer<String> observer = mock(Observer.class);
+        result.observe(mOwner, observer);
+        verify(observer, never()).onChanged(anyString());
+        first.setValue("first");
+        trigger.setValue(1);
+        verify(observer).onChanged("first");
+        reset(observer);
+
+        trigger.setValue(2);
+        verify(observer, never()).onChanged(anyString());
+        assertThat(first.hasObservers(), is(false));
+    }
+
+    @Test
+    public void noObsoleteValueTest() {
+        MutableLiveData<Integer> numbers = new MutableLiveData<>();
+        LiveData<Integer> squared = Transformations.map(numbers, new Function<Integer, Integer>() {
+            @Override
+            public Integer apply(Integer input) {
+                return input * input;
+            }
+        });
+
+        Observer observer = mock(Observer.class);
+        squared.setValue(1);
+        squared.observeForever(observer);
+        verify(observer).onChanged(1);
+        squared.removeObserver(observer);
+        reset(observer);
+        numbers.setValue(2);
+        squared.observeForever(observer);
+        verify(observer, only()).onChanged(4);
+    }
+}
diff --git a/lifecycle/livedata/src/test/java/androidx/lifecycle/util/InstantTaskExecutor.java b/lifecycle/livedata/src/test/java/androidx/lifecycle/util/InstantTaskExecutor.java
new file mode 100644
index 0000000..60d703f
--- /dev/null
+++ b/lifecycle/livedata/src/test/java/androidx/lifecycle/util/InstantTaskExecutor.java
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.lifecycle.util;
+
+import androidx.arch.core.executor.TaskExecutor;
+
+public class InstantTaskExecutor extends TaskExecutor {
+    @Override
+    public void executeOnDiskIO(Runnable runnable) {
+        runnable.run();
+    }
+
+    @Override
+    public void postToMainThread(Runnable runnable) {
+        runnable.run();
+    }
+
+    @Override
+    public boolean isMainThread() {
+        return true;
+    }
+}
diff --git a/lifecycle/reactivestreams/api/current.txt b/lifecycle/reactivestreams/api/current.txt
index 4a509e9..d342cca 100644
--- a/lifecycle/reactivestreams/api/current.txt
+++ b/lifecycle/reactivestreams/api/current.txt
@@ -1,8 +1,8 @@
-package android.arch.lifecycle {
+package androidx.lifecycle {
 
   public final class LiveDataReactiveStreams {
-    method public static <T> android.arch.lifecycle.LiveData<T> fromPublisher(org.reactivestreams.Publisher<T>);
-    method public static <T> org.reactivestreams.Publisher<T> toPublisher(android.arch.lifecycle.LifecycleOwner, android.arch.lifecycle.LiveData<T>);
+    method public static <T> androidx.lifecycle.LiveData<T> fromPublisher(org.reactivestreams.Publisher<T>);
+    method public static <T> org.reactivestreams.Publisher<T> toPublisher(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.LiveData<T>);
   }
 
 }
diff --git a/lifecycle/reactivestreams/api/1.0.0.txt b/lifecycle/reactivestreams/api_legacy/1.0.0.txt
similarity index 100%
rename from lifecycle/reactivestreams/api/1.0.0.txt
rename to lifecycle/reactivestreams/api_legacy/1.0.0.txt
diff --git a/lifecycle/reactivestreams/api/1.1.0.txt b/lifecycle/reactivestreams/api_legacy/1.1.0.txt
similarity index 100%
rename from lifecycle/reactivestreams/api/1.1.0.txt
rename to lifecycle/reactivestreams/api_legacy/1.1.0.txt
diff --git a/lifecycle/reactivestreams/api/1.0.0.txt b/lifecycle/reactivestreams/api_legacy/current.txt
similarity index 100%
copy from lifecycle/reactivestreams/api/1.0.0.txt
copy to lifecycle/reactivestreams/api_legacy/current.txt
diff --git a/lifecycle/reactivestreams/build.gradle b/lifecycle/reactivestreams/build.gradle
index 12f12b1..34390b7 100644
--- a/lifecycle/reactivestreams/build.gradle
+++ b/lifecycle/reactivestreams/build.gradle
@@ -24,10 +24,10 @@
 }
 
 dependencies {
-    api(project(":arch:common"))
-    api(project(":lifecycle:common"))
-    api(project(":lifecycle:livedata"))
-    api(project(":lifecycle:runtime"))
+    api(project(":arch:core-common"))
+    api(project(":lifecycle:lifecycle-common"))
+    api(project(":lifecycle:lifecycle-livedata"))
+    api(project(":lifecycle:lifecycle-runtime"))
     api(SUPPORT_ANNOTATIONS)
     api(REACTIVE_STREAMS)
 
diff --git a/lifecycle/reactivestreams/src/androidTest/AndroidManifest.xml b/lifecycle/reactivestreams/src/androidTest/AndroidManifest.xml
index e60c9f4..304fd93 100644
--- a/lifecycle/reactivestreams/src/androidTest/AndroidManifest.xml
+++ b/lifecycle/reactivestreams/src/androidTest/AndroidManifest.xml
@@ -16,11 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle.reactivestreams.test">
+          package="androidx.lifecycle.reactivestreams.test">
     <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
 
     <application>
-        <activity android:name="android.arch.lifecycle.viewmodeltest.ViewModelActivity"
+        <activity android:name="androidx.lifecycle.viewmodeltest.ViewModelActivity"
                   android:theme="@style/Base.Theme.AppCompat">
         </activity>
     </application>
diff --git a/lifecycle/reactivestreams/src/main/AndroidManifest.xml b/lifecycle/reactivestreams/src/main/AndroidManifest.xml
index 2210041..96fed76 100644
--- a/lifecycle/reactivestreams/src/main/AndroidManifest.xml
+++ b/lifecycle/reactivestreams/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle.reactivestreams">
+          package="androidx.lifecycle.reactivestreams">
 </manifest>
diff --git a/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java b/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
deleted file mode 100644
index ed3c57c..0000000
--- a/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.reactivestreams.Publisher;
-import org.reactivestreams.Subscriber;
-import org.reactivestreams.Subscription;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Adapts {@link LiveData} input and output to the ReactiveStreams spec.
- */
-@SuppressWarnings("WeakerAccess")
-public final class LiveDataReactiveStreams {
-    private LiveDataReactiveStreams() {
-    }
-
-    /**
-     * Adapts the given {@link LiveData} stream to a ReactiveStreams {@link Publisher}.
-     *
-     * <p>
-     * By using a good publisher implementation such as RxJava 2.x Flowables, most consumers will
-     * be able to let the library deal with backpressure using operators and not need to worry about
-     * ever manually calling {@link Subscription#request}.
-     *
-     * <p>
-     * On subscription to the publisher, the observer will attach to the given {@link LiveData}.
-     * Once {@link Subscription#request) is called on the subscription object, an observer will be
-     * connected to the data stream. Calling request(Long.MAX_VALUE) is equivalent to creating an
-     * unbounded stream with no backpressure. If request with a finite count reaches 0, the observer
-     * will buffer the latest item and emit it to the subscriber when data is again requested. Any
-     * other items emitted during the time there was no backpressure requested will be dropped.
-     */
-    @NonNull
-    public static <T> Publisher<T> toPublisher(
-            @NonNull LifecycleOwner lifecycle, @NonNull LiveData<T> liveData) {
-
-        return new LiveDataPublisher<>(lifecycle, liveData);
-    }
-
-    private static final class LiveDataPublisher<T> implements Publisher<T> {
-        final LifecycleOwner mLifecycle;
-        final LiveData<T> mLiveData;
-
-        LiveDataPublisher(LifecycleOwner lifecycle, LiveData<T> liveData) {
-            this.mLifecycle = lifecycle;
-            this.mLiveData = liveData;
-        }
-
-        @Override
-        public void subscribe(Subscriber<? super T> subscriber) {
-            subscriber.onSubscribe(new LiveDataSubscription<T>(subscriber, mLifecycle, mLiveData));
-        }
-
-        static final class LiveDataSubscription<T> implements Subscription, Observer<T> {
-            final Subscriber<? super T> mSubscriber;
-            final LifecycleOwner mLifecycle;
-            final LiveData<T> mLiveData;
-
-            volatile boolean mCanceled;
-            // used on main thread only
-            boolean mObserving;
-            long mRequested;
-            // used on main thread only
-            @Nullable
-            T mLatest;
-
-            LiveDataSubscription(final Subscriber<? super T> subscriber,
-                    final LifecycleOwner lifecycle, final LiveData<T> liveData) {
-                this.mSubscriber = subscriber;
-                this.mLifecycle = lifecycle;
-                this.mLiveData = liveData;
-            }
-
-            @Override
-            public void onChanged(@Nullable T t) {
-                if (mCanceled) {
-                    return;
-                }
-                if (mRequested > 0) {
-                    mLatest = null;
-                    mSubscriber.onNext(t);
-                    if (mRequested != Long.MAX_VALUE) {
-                        mRequested--;
-                    }
-                } else {
-                    mLatest = t;
-                }
-            }
-
-            @Override
-            public void request(final long n) {
-                if (mCanceled) {
-                    return;
-                }
-                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mCanceled) {
-                            return;
-                        }
-                        if (n <= 0L) {
-                            mCanceled = true;
-                            if (mObserving) {
-                                mLiveData.removeObserver(LiveDataSubscription.this);
-                                mObserving = false;
-                            }
-                            mLatest = null;
-                            mSubscriber.onError(
-                                    new IllegalArgumentException("Non-positive request"));
-                            return;
-                        }
-
-                        // Prevent overflowage.
-                        mRequested = mRequested + n >= mRequested
-                                ? mRequested + n : Long.MAX_VALUE;
-                        if (!mObserving) {
-                            mObserving = true;
-                            mLiveData.observe(mLifecycle, LiveDataSubscription.this);
-                        } else if (mLatest != null) {
-                            onChanged(mLatest);
-                            mLatest = null;
-                        }
-                    }
-                });
-            }
-
-            @Override
-            public void cancel() {
-                if (mCanceled) {
-                    return;
-                }
-                mCanceled = true;
-                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mObserving) {
-                            mLiveData.removeObserver(LiveDataSubscription.this);
-                            mObserving = false;
-                        }
-                        mLatest = null;
-                    }
-                });
-            }
-        }
-    }
-
-    /**
-     * 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.
-     * <p>
-     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
-     * in the data that's held. In case of an error being emitted by the publisher, an error will
-     * be propagated to the main thread and the app will crash.
-     *
-     * @param <T> The type of data hold by this instance.
-     */
-    @NonNull
-    public static <T> LiveData<T> fromPublisher(@NonNull Publisher<T> publisher) {
-        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.
-     *
-     * <p>
-     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
-     * in the data that's held. In case of an error being emitted by the publisher, an error will
-     * be propagated to the main thread and the app will crash.
-     *
-     * @param <T> The type of data hold by this instance.
-     */
-    private static class PublisherLiveData<T> extends LiveData<T> {
-        private final Publisher<T> mPublisher;
-        final AtomicReference<LiveDataSubscriber> mSubscriber;
-
-        PublisherLiveData(@NonNull Publisher<T> publisher) {
-            mPublisher = publisher;
-            mSubscriber = new AtomicReference<>();
-        }
-
-        @Override
-        protected void onActive() {
-            super.onActive();
-            LiveDataSubscriber s = new LiveDataSubscriber();
-            mSubscriber.set(s);
-            mPublisher.subscribe(s);
-        }
-
-        @Override
-        protected void onInactive() {
-            super.onInactive();
-            LiveDataSubscriber s = mSubscriber.getAndSet(null);
-            if (s != null) {
-                s.cancelSubscription();
-            }
-        }
-
-        final class LiveDataSubscriber extends AtomicReference<Subscription>
-                implements Subscriber<T> {
-
-            @Override
-            public void onSubscribe(Subscription s) {
-                if (compareAndSet(null, s)) {
-                    s.request(Long.MAX_VALUE);
-                } else {
-                    s.cancel();
-                }
-            }
-
-            @Override
-            public void onNext(T item) {
-                postValue(item);
-            }
-
-            @Override
-            public void onError(final Throwable ex) {
-                mSubscriber.compareAndSet(this, null);
-
-                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Errors should be handled upstream, so propagate as a crash.
-                        throw new RuntimeException("LiveData does not handle errors. Errors from "
-                                + "publishers should be handled upstream and propagated as "
-                                + "state", ex);
-                    }
-                });
-            }
-
-            @Override
-            public void onComplete() {
-                mSubscriber.compareAndSet(this, null);
-            }
-
-            public void cancelSubscription() {
-                Subscription s = get();
-                if (s != null) {
-                    s.cancel();
-                }
-            }
-        }
-    }
-}
diff --git a/lifecycle/reactivestreams/src/main/java/androidx/lifecycle/LiveDataReactiveStreams.java b/lifecycle/reactivestreams/src/main/java/androidx/lifecycle/LiveDataReactiveStreams.java
new file mode 100644
index 0000000..4af5a09
--- /dev/null
+++ b/lifecycle/reactivestreams/src/main/java/androidx/lifecycle/LiveDataReactiveStreams.java
@@ -0,0 +1,284 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Adapts {@link LiveData} input and output to the ReactiveStreams spec.
+ */
+@SuppressWarnings("WeakerAccess")
+public final class LiveDataReactiveStreams {
+    private LiveDataReactiveStreams() {
+    }
+
+    /**
+     * Adapts the given {@link LiveData} stream to a ReactiveStreams {@link Publisher}.
+     *
+     * <p>
+     * By using a good publisher implementation such as RxJava 2.x Flowables, most consumers will
+     * be able to let the library deal with backpressure using operators and not need to worry about
+     * ever manually calling {@link Subscription#request}.
+     *
+     * <p>
+     * On subscription to the publisher, the observer will attach to the given {@link LiveData}.
+     * Once {@link Subscription#request) is called on the subscription object, an observer will be
+     * connected to the data stream. Calling request(Long.MAX_VALUE) is equivalent to creating an
+     * unbounded stream with no backpressure. If request with a finite count reaches 0, the observer
+     * will buffer the latest item and emit it to the subscriber when data is again requested. Any
+     * other items emitted during the time there was no backpressure requested will be dropped.
+     */
+    @NonNull
+    public static <T> Publisher<T> toPublisher(
+            @NonNull LifecycleOwner lifecycle, @NonNull LiveData<T> liveData) {
+
+        return new LiveDataPublisher<>(lifecycle, liveData);
+    }
+
+    private static final class LiveDataPublisher<T> implements Publisher<T> {
+        final LifecycleOwner mLifecycle;
+        final LiveData<T> mLiveData;
+
+        LiveDataPublisher(LifecycleOwner lifecycle, LiveData<T> liveData) {
+            this.mLifecycle = lifecycle;
+            this.mLiveData = liveData;
+        }
+
+        @Override
+        public void subscribe(Subscriber<? super T> subscriber) {
+            subscriber.onSubscribe(new LiveDataSubscription<T>(subscriber, mLifecycle, mLiveData));
+        }
+
+        static final class LiveDataSubscription<T> implements Subscription, Observer<T> {
+            final Subscriber<? super T> mSubscriber;
+            final LifecycleOwner mLifecycle;
+            final LiveData<T> mLiveData;
+
+            volatile boolean mCanceled;
+            // used on main thread only
+            boolean mObserving;
+            long mRequested;
+            // used on main thread only
+            @Nullable
+            T mLatest;
+
+            LiveDataSubscription(final Subscriber<? super T> subscriber,
+                    final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+                this.mSubscriber = subscriber;
+                this.mLifecycle = lifecycle;
+                this.mLiveData = liveData;
+            }
+
+            @Override
+            public void onChanged(@Nullable T t) {
+                if (mCanceled) {
+                    return;
+                }
+                if (mRequested > 0) {
+                    mLatest = null;
+                    mSubscriber.onNext(t);
+                    if (mRequested != Long.MAX_VALUE) {
+                        mRequested--;
+                    }
+                } else {
+                    mLatest = t;
+                }
+            }
+
+            @Override
+            public void request(final long n) {
+                if (mCanceled) {
+                    return;
+                }
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCanceled) {
+                            return;
+                        }
+                        if (n <= 0L) {
+                            mCanceled = true;
+                            if (mObserving) {
+                                mLiveData.removeObserver(LiveDataSubscription.this);
+                                mObserving = false;
+                            }
+                            mLatest = null;
+                            mSubscriber.onError(
+                                    new IllegalArgumentException("Non-positive request"));
+                            return;
+                        }
+
+                        // Prevent overflowage.
+                        mRequested = mRequested + n >= mRequested
+                                ? mRequested + n : Long.MAX_VALUE;
+                        if (!mObserving) {
+                            mObserving = true;
+                            mLiveData.observe(mLifecycle, LiveDataSubscription.this);
+                        } else if (mLatest != null) {
+                            onChanged(mLatest);
+                            mLatest = null;
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void cancel() {
+                if (mCanceled) {
+                    return;
+                }
+                mCanceled = true;
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mObserving) {
+                            mLiveData.removeObserver(LiveDataSubscription.this);
+                            mObserving = false;
+                        }
+                        mLatest = null;
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * 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.
+     * <p>
+     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+     * in the data that's held. In case of an error being emitted by the publisher, an error will
+     * be propagated to the main thread and the app will crash.
+     *
+     * @param <T> The type of data hold by this instance.
+     */
+    @NonNull
+    public static <T> LiveData<T> fromPublisher(@NonNull Publisher<T> publisher) {
+        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.
+     *
+     * <p>
+     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+     * in the data that's held. In case of an error being emitted by the publisher, an error will
+     * be propagated to the main thread and the app will crash.
+     *
+     * @param <T> The type of data hold by this instance.
+     */
+    private static class PublisherLiveData<T> extends LiveData<T> {
+        private final Publisher<T> mPublisher;
+        final AtomicReference<LiveDataSubscriber> mSubscriber;
+
+        PublisherLiveData(@NonNull Publisher<T> publisher) {
+            mPublisher = publisher;
+            mSubscriber = new AtomicReference<>();
+        }
+
+        @Override
+        protected void onActive() {
+            super.onActive();
+            LiveDataSubscriber s = new LiveDataSubscriber();
+            mSubscriber.set(s);
+            mPublisher.subscribe(s);
+        }
+
+        @Override
+        protected void onInactive() {
+            super.onInactive();
+            LiveDataSubscriber s = mSubscriber.getAndSet(null);
+            if (s != null) {
+                s.cancelSubscription();
+            }
+        }
+
+        final class LiveDataSubscriber extends AtomicReference<Subscription>
+                implements Subscriber<T> {
+
+            @Override
+            public void onSubscribe(Subscription s) {
+                if (compareAndSet(null, s)) {
+                    s.request(Long.MAX_VALUE);
+                } else {
+                    s.cancel();
+                }
+            }
+
+            @Override
+            public void onNext(T item) {
+                postValue(item);
+            }
+
+            @Override
+            public void onError(final Throwable ex) {
+                mSubscriber.compareAndSet(this, null);
+
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Errors should be handled upstream, so propagate as a crash.
+                        throw new RuntimeException("LiveData does not handle errors. Errors from "
+                                + "publishers should be handled upstream and propagated as "
+                                + "state", ex);
+                    }
+                });
+            }
+
+            @Override
+            public void onComplete() {
+                mSubscriber.compareAndSet(this, null);
+            }
+
+            public void cancelSubscription() {
+                Subscription s = get();
+                if (s != null) {
+                    s.cancel();
+                }
+            }
+        }
+    }
+}
diff --git a/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
deleted file mode 100644
index 163cff0..0000000
--- a/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
-import android.support.annotation.Nullable;
-import android.support.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.reactivestreams.Subscriber;
-import org.reactivestreams.Subscription;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import io.reactivex.Flowable;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.functions.Consumer;
-import io.reactivex.processors.PublishProcessor;
-import io.reactivex.processors.ReplayProcessor;
-import io.reactivex.schedulers.TestScheduler;
-import io.reactivex.subjects.AsyncSubject;
-
-@SmallTest
-public class LiveDataReactiveStreamsTest {
-    private LifecycleOwner mLifecycleOwner;
-
-    private final List<String> mLiveDataOutput = new ArrayList<>();
-    private final Observer<String> mObserver = new Observer<String>() {
-        @Override
-        public void onChanged(@Nullable String s) {
-            mLiveDataOutput.add(s);
-        }
-    };
-
-    private final ReplayProcessor<String> mOutputProcessor = ReplayProcessor.create();
-
-    private static final TestScheduler sBackgroundScheduler = new TestScheduler();
-    private Thread mTestThread;
-
-    @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();
-        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
-
-            @Override
-            public void executeOnDiskIO(Runnable runnable) {
-                throw new IllegalStateException();
-            }
-
-            @Override
-            public void postToMainThread(Runnable runnable) {
-                // Wrong implementation, but it is fine for test
-                runnable.run();
-            }
-
-            @Override
-            public boolean isMainThread() {
-                return Thread.currentThread() == mTestThread;
-            }
-
-        });
-    }
-
-    @After
-    public void removeExecutorDelegate() {
-        ArchTaskExecutor.getInstance().setDelegate(null);
-    }
-
-    @Test
-    public void convertsFromPublisher() {
-        PublishProcessor<String> processor = PublishProcessor.create();
-        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
-
-        liveData.observe(mLifecycleOwner, mObserver);
-
-        processor.onNext("foo");
-        processor.onNext("bar");
-        processor.onNext("baz");
-
-        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
-    }
-
-    @Test
-    public void convertsFromPublisherSubscribeWithDelay() {
-        PublishProcessor<String> processor = PublishProcessor.create();
-        processor.delaySubscription(100, TimeUnit.SECONDS, sBackgroundScheduler);
-        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
-
-        liveData.observe(mLifecycleOwner, mObserver);
-
-        processor.onNext("foo");
-        liveData.removeObserver(mObserver);
-        sBackgroundScheduler.triggerActions();
-        liveData.observe(mLifecycleOwner, mObserver);
-
-        processor.onNext("bar");
-        processor.onNext("baz");
-
-        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "bar", "baz")));
-    }
-
-    @Test
-    public void convertsFromPublisherThrowsException() {
-        PublishProcessor<String> processor = PublishProcessor.create();
-        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
-
-        liveData.observe(mLifecycleOwner, mObserver);
-
-        IllegalStateException exception = new IllegalStateException("test exception");
-        try {
-            processor.onError(exception);
-            fail("Runtime Exception expected");
-        } catch (RuntimeException ex) {
-            assertEquals(ex.getCause(), exception);
-        }
-    }
-
-    @Test
-    public void convertsFromPublisherWithMultipleObservers() {
-        final List<String> output2 = new ArrayList<>();
-        PublishProcessor<String> processor = PublishProcessor.create();
-        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
-
-        liveData.observe(mLifecycleOwner, mObserver);
-
-        processor.onNext("foo");
-        processor.onNext("bar");
-
-        // The second observer should only get the newest value and any later values.
-        liveData.observe(mLifecycleOwner, new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                output2.add(s);
-            }
-        });
-
-        processor.onNext("baz");
-
-        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
-        assertThat(output2, is(Arrays.asList("bar", "baz")));
-    }
-
-    @Test
-    public void convertsFromPublisherWithMultipleObserversAfterInactive() {
-        final List<String> output2 = new ArrayList<>();
-        PublishProcessor<String> processor = PublishProcessor.create();
-        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
-
-        liveData.observe(mLifecycleOwner, mObserver);
-
-        processor.onNext("foo");
-        processor.onNext("bar");
-
-        // The second observer should only get the newest value and any later values.
-        liveData.observe(mLifecycleOwner, new Observer<String>() {
-            @Override
-            public void onChanged(@Nullable String s) {
-                output2.add(s);
-            }
-        });
-
-        liveData.removeObserver(mObserver);
-        processor.onNext("baz");
-
-        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar")));
-        assertThat(output2, is(Arrays.asList("bar", "baz")));
-    }
-
-    @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 convertsFromPublisherManagesSubscriptions() {
-        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(mLifecycleOwner, mObserver);
-
-        assertThat(mLiveDataOutput, is(Collections.singletonList("foo")));
-        sBackgroundScheduler.triggerActions();
-        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
-    }
-
-    @Test
-    public void convertsToPublisherWithSyncData() {
-        MutableLiveData<String> liveData = new MutableLiveData<>();
-        liveData.setValue("foo");
-        assertThat(liveData.getValue(), is("foo"));
-
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
-                .subscribe(mOutputProcessor);
-
-        liveData.setValue("bar");
-        liveData.setValue("baz");
-
-        assertThat(
-                mOutputProcessor.getValues(new String[]{}),
-                is(new String[]{"foo", "bar", "baz"}));
-    }
-
-    @Test
-    public void convertingToPublisherIsCancelable() {
-        MutableLiveData<String> liveData = new MutableLiveData<>();
-        liveData.setValue("foo");
-        assertThat(liveData.getValue(), is("foo"));
-
-        Disposable disposable = Flowable
-                .fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
-                .subscribe(new Consumer<String>() {
-                    @Override
-                    public void accept(String s) throws Exception {
-                        mLiveDataOutput.add(s);
-                    }
-                });
-
-        liveData.setValue("bar");
-        liveData.setValue("baz");
-
-        assertThat(liveData.hasObservers(), is(true));
-        disposable.dispose();
-
-        liveData.setValue("fizz");
-        liveData.setValue("buzz");
-
-        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
-        // Canceling disposable should also remove livedata mObserver.
-        assertThat(liveData.hasObservers(), is(false));
-    }
-
-    @Test
-    public void convertsToPublisherWithBackpressure() {
-        MutableLiveData<String> liveData = new MutableLiveData<>();
-
-        final AsyncSubject<Subscription> subscriptionSubject = AsyncSubject.create();
-
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
-                .subscribe(new Subscriber<String>() {
-                    @Override
-                    public void onSubscribe(Subscription s) {
-                        subscriptionSubject.onNext(s);
-                        subscriptionSubject.onComplete();
-                    }
-
-                    @Override
-                    public void onNext(String s) {
-                        mOutputProcessor.onNext(s);
-                    }
-
-                    @Override
-                    public void onError(Throwable t) {
-                        throw new RuntimeException(t);
-                    }
-
-                    @Override
-                    public void onComplete() {
-                    }
-                });
-
-        // Subscription should have happened synchronously. If it didn't, this will deadlock.
-        final Subscription subscription = subscriptionSubject.blockingSingle();
-
-        subscription.request(1);
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
-
-        liveData.setValue("foo");
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
-
-        subscription.request(2);
-        liveData.setValue("baz");
-        liveData.setValue("fizz");
-
-        assertThat(
-                mOutputProcessor.getValues(new String[]{}),
-                is(new String[]{"foo", "baz", "fizz"}));
-
-        // 'nyan' will be dropped as there is nothing currently requesting a stream.
-        liveData.setValue("nyan");
-        liveData.setValue("cat");
-
-        assertThat(
-                mOutputProcessor.getValues(new String[]{}),
-                is(new String[]{"foo", "baz", "fizz"}));
-
-        // When a new request comes in, the latest value will be pushed.
-        subscription.request(1);
-        assertThat(
-                mOutputProcessor.getValues(new String[]{}),
-                is(new String[]{"foo", "baz", "fizz", "cat"}));
-    }
-
-    @Test
-    public void convertsToPublisherWithAsyncData() {
-        MutableLiveData<String> liveData = new MutableLiveData<>();
-
-        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
-                .observeOn(sBackgroundScheduler)
-                .subscribe(mOutputProcessor);
-
-        liveData.setValue("foo");
-
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
-        sBackgroundScheduler.triggerActions();
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
-
-        liveData.setValue("bar");
-        liveData.setValue("baz");
-
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
-        sBackgroundScheduler.triggerActions();
-        assertThat(mOutputProcessor.getValues(
-                new String[]{}),
-                is(new String[]{"foo", "bar", "baz"}));
-    }
-}
diff --git a/lifecycle/reactivestreams/src/test/java/androidx/lifecycle/LiveDataReactiveStreamsTest.java b/lifecycle/reactivestreams/src/test/java/androidx/lifecycle/LiveDataReactiveStreamsTest.java
new file mode 100644
index 0000000..6311d94
--- /dev/null
+++ b/lifecycle/reactivestreams/src/test/java/androidx/lifecycle/LiveDataReactiveStreamsTest.java
@@ -0,0 +1,382 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.Flowable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.processors.PublishProcessor;
+import io.reactivex.processors.ReplayProcessor;
+import io.reactivex.schedulers.TestScheduler;
+import io.reactivex.subjects.AsyncSubject;
+
+@SmallTest
+public class LiveDataReactiveStreamsTest {
+    private LifecycleOwner mLifecycleOwner;
+
+    private final List<String> mLiveDataOutput = new ArrayList<>();
+    private final Observer<String> mObserver = new Observer<String>() {
+        @Override
+        public void onChanged(@Nullable String s) {
+            mLiveDataOutput.add(s);
+        }
+    };
+
+    private final ReplayProcessor<String> mOutputProcessor = ReplayProcessor.create();
+
+    private static final TestScheduler sBackgroundScheduler = new TestScheduler();
+    private Thread mTestThread;
+
+    @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();
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                // Wrong implementation, but it is fine for test
+                runnable.run();
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return Thread.currentThread() == mTestThread;
+            }
+
+        });
+    }
+
+    @After
+    public void removeExecutorDelegate() {
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    @Test
+    public void convertsFromPublisher() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        processor.onNext("bar");
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherSubscribeWithDelay() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        processor.delaySubscription(100, TimeUnit.SECONDS, sBackgroundScheduler);
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        liveData.removeObserver(mObserver);
+        sBackgroundScheduler.triggerActions();
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("bar");
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "bar", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherThrowsException() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        IllegalStateException exception = new IllegalStateException("test exception");
+        try {
+            processor.onError(exception);
+            fail("Runtime Exception expected");
+        } catch (RuntimeException ex) {
+            assertEquals(ex.getCause(), exception);
+        }
+    }
+
+    @Test
+    public void convertsFromPublisherWithMultipleObservers() {
+        final List<String> output2 = new ArrayList<>();
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        processor.onNext("bar");
+
+        // The second observer should only get the newest value and any later values.
+        liveData.observe(mLifecycleOwner, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                output2.add(s);
+            }
+        });
+
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+        assertThat(output2, is(Arrays.asList("bar", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherWithMultipleObserversAfterInactive() {
+        final List<String> output2 = new ArrayList<>();
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        processor.onNext("bar");
+
+        // The second observer should only get the newest value and any later values.
+        liveData.observe(mLifecycleOwner, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                output2.add(s);
+            }
+        });
+
+        liveData.removeObserver(mObserver);
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar")));
+        assertThat(output2, is(Arrays.asList("bar", "baz")));
+    }
+
+    @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 convertsFromPublisherManagesSubscriptions() {
+        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(mLifecycleOwner, mObserver);
+
+        assertThat(mLiveDataOutput, is(Collections.singletonList("foo")));
+        sBackgroundScheduler.triggerActions();
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+    }
+
+    @Test
+    public void convertsToPublisherWithSyncData() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+        liveData.setValue("foo");
+        assertThat(liveData.getValue(), is("foo"));
+
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
+                .subscribe(mOutputProcessor);
+
+        liveData.setValue("bar");
+        liveData.setValue("baz");
+
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[]{"foo", "bar", "baz"}));
+    }
+
+    @Test
+    public void convertingToPublisherIsCancelable() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+        liveData.setValue("foo");
+        assertThat(liveData.getValue(), is("foo"));
+
+        Disposable disposable = Flowable
+                .fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
+                .subscribe(new Consumer<String>() {
+                    @Override
+                    public void accept(String s) throws Exception {
+                        mLiveDataOutput.add(s);
+                    }
+                });
+
+        liveData.setValue("bar");
+        liveData.setValue("baz");
+
+        assertThat(liveData.hasObservers(), is(true));
+        disposable.dispose();
+
+        liveData.setValue("fizz");
+        liveData.setValue("buzz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar", "baz")));
+        // Canceling disposable should also remove livedata mObserver.
+        assertThat(liveData.hasObservers(), is(false));
+    }
+
+    @Test
+    public void convertsToPublisherWithBackpressure() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+
+        final AsyncSubject<Subscription> subscriptionSubject = AsyncSubject.create();
+
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
+                .subscribe(new Subscriber<String>() {
+                    @Override
+                    public void onSubscribe(Subscription s) {
+                        subscriptionSubject.onNext(s);
+                        subscriptionSubject.onComplete();
+                    }
+
+                    @Override
+                    public void onNext(String s) {
+                        mOutputProcessor.onNext(s);
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        throw new RuntimeException(t);
+                    }
+
+                    @Override
+                    public void onComplete() {
+                    }
+                });
+
+        // Subscription should have happened synchronously. If it didn't, this will deadlock.
+        final Subscription subscription = subscriptionSubject.blockingSingle();
+
+        subscription.request(1);
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
+
+        liveData.setValue("foo");
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
+
+        subscription.request(2);
+        liveData.setValue("baz");
+        liveData.setValue("fizz");
+
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[]{"foo", "baz", "fizz"}));
+
+        // 'nyan' will be dropped as there is nothing currently requesting a stream.
+        liveData.setValue("nyan");
+        liveData.setValue("cat");
+
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[]{"foo", "baz", "fizz"}));
+
+        // When a new request comes in, the latest value will be pushed.
+        subscription.request(1);
+        assertThat(
+                mOutputProcessor.getValues(new String[]{}),
+                is(new String[]{"foo", "baz", "fizz", "cat"}));
+    }
+
+    @Test
+    public void convertsToPublisherWithAsyncData() {
+        MutableLiveData<String> liveData = new MutableLiveData<>();
+
+        Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
+                .observeOn(sBackgroundScheduler)
+                .subscribe(mOutputProcessor);
+
+        liveData.setValue("foo");
+
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
+        sBackgroundScheduler.triggerActions();
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
+
+        liveData.setValue("bar");
+        liveData.setValue("baz");
+
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
+        sBackgroundScheduler.triggerActions();
+        assertThat(mOutputProcessor.getValues(
+                new String[]{}),
+                is(new String[]{"foo", "bar", "baz"}));
+    }
+}
diff --git a/lifecycle/runtime/api/current.txt b/lifecycle/runtime/api/current.txt
index 2b900b0..84da3d1 100644
--- a/lifecycle/runtime/api/current.txt
+++ b/lifecycle/runtime/api/current.txt
@@ -1,17 +1,17 @@
-package android.arch.lifecycle {
+package androidx.lifecycle {
 
-  public class LifecycleRegistry extends android.arch.lifecycle.Lifecycle {
-    ctor public LifecycleRegistry(android.arch.lifecycle.LifecycleOwner);
-    method public void addObserver(android.arch.lifecycle.LifecycleObserver);
-    method public android.arch.lifecycle.Lifecycle.State getCurrentState();
+  public class LifecycleRegistry extends androidx.lifecycle.Lifecycle {
+    ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner);
+    method public void addObserver(androidx.lifecycle.LifecycleObserver);
+    method public androidx.lifecycle.Lifecycle.State getCurrentState();
     method public int getObserverCount();
-    method public void handleLifecycleEvent(android.arch.lifecycle.Lifecycle.Event);
-    method public void markState(android.arch.lifecycle.Lifecycle.State);
-    method public void removeObserver(android.arch.lifecycle.LifecycleObserver);
+    method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event);
+    method public void markState(androidx.lifecycle.Lifecycle.State);
+    method public void removeObserver(androidx.lifecycle.LifecycleObserver);
   }
 
-  public abstract deprecated interface LifecycleRegistryOwner implements android.arch.lifecycle.LifecycleOwner {
-    method public abstract android.arch.lifecycle.LifecycleRegistry getLifecycle();
+  public abstract deprecated interface LifecycleRegistryOwner implements androidx.lifecycle.LifecycleOwner {
+    method public abstract androidx.lifecycle.LifecycleRegistry getLifecycle();
   }
 
 }
diff --git a/lifecycle/runtime/api/0.0.0.txt b/lifecycle/runtime/api_legacy/0.0.0.txt
similarity index 100%
rename from lifecycle/runtime/api/0.0.0.txt
rename to lifecycle/runtime/api_legacy/0.0.0.txt
diff --git a/lifecycle/runtime/api/1.0.0.txt b/lifecycle/runtime/api_legacy/1.0.0.txt
similarity index 100%
rename from lifecycle/runtime/api/1.0.0.txt
rename to lifecycle/runtime/api_legacy/1.0.0.txt
diff --git a/lifecycle/runtime/api/1.1.0.txt b/lifecycle/runtime/api_legacy/1.1.0.txt
similarity index 100%
rename from lifecycle/runtime/api/1.1.0.txt
rename to lifecycle/runtime/api_legacy/1.1.0.txt
diff --git a/lifecycle/runtime/api/1.0.0.txt b/lifecycle/runtime/api_legacy/current.txt
similarity index 100%
copy from lifecycle/runtime/api/1.0.0.txt
copy to lifecycle/runtime/api_legacy/current.txt
diff --git a/lifecycle/runtime/build.gradle b/lifecycle/runtime/build.gradle
index f7f6ff8..baa2b32 100644
--- a/lifecycle/runtime/build.gradle
+++ b/lifecycle/runtime/build.gradle
@@ -14,8 +14,8 @@
 }
 
 dependencies {
-    api(project(":lifecycle:common"))
-    api(project(":arch:common"))
+    api(project(":lifecycle:lifecycle-common"))
+    api(project(":arch:core-common"))
     // necessary for IJ to resolve dependencies.
     api(SUPPORT_ANNOTATIONS)
 
diff --git a/lifecycle/runtime/proguard-rules.pro b/lifecycle/runtime/proguard-rules.pro
index 5e61405..982e05b 100644
--- a/lifecycle/runtime/proguard-rules.pro
+++ b/lifecycle/runtime/proguard-rules.pro
@@ -1,16 +1,16 @@
 -keepattributes *Annotation*
 
--keepclassmembers enum android.arch.lifecycle.Lifecycle$Event {
+-keepclassmembers enum androidx.lifecycle.Lifecycle$Event {
     <fields>;
 }
 
--keep class * implements android.arch.lifecycle.LifecycleObserver {
+-keep class * implements androidx.lifecycle.LifecycleObserver {
 }
 
--keep class * implements android.arch.lifecycle.GeneratedAdapter {
+-keep class * implements androidx.lifecycle.GeneratedAdapter {
     <init>(...);
 }
 
 -keepclassmembers class ** {
-    @android.arch.lifecycle.OnLifecycleEvent *;
+    @androidx.lifecycle.OnLifecycleEvent *;
 }
\ No newline at end of file
diff --git a/lifecycle/runtime/src/androidTest/java/android/arch/lifecycle/MissingClassTest.java b/lifecycle/runtime/src/androidTest/java/android/arch/lifecycle/MissingClassTest.java
deleted file mode 100644
index 81a0756..0000000
--- a/lifecycle/runtime/src/androidTest/java/android/arch/lifecycle/MissingClassTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.PictureInPictureParams;
-import android.os.Build;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
-@SmallTest
-public class MissingClassTest {
-    public static class ObserverWithMissingClasses {
-        @SuppressWarnings("unused")
-        public void newApiMethod(PictureInPictureParams params) {}
-
-        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
-        public void onResume() {}
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testMissingApi() {
-        new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
-    }
-}
diff --git a/lifecycle/runtime/src/androidTest/java/androidx/lifecycle/MissingClassTest.java b/lifecycle/runtime/src/androidTest/java/androidx/lifecycle/MissingClassTest.java
new file mode 100644
index 0000000..1992f79
--- /dev/null
+++ b/lifecycle/runtime/src/androidTest/java/androidx/lifecycle/MissingClassTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle;
+
+import android.app.PictureInPictureParams;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@SmallTest
+public class MissingClassTest {
+    public static class ObserverWithMissingClasses {
+        @SuppressWarnings("unused")
+        public void newApiMethod(PictureInPictureParams params) {}
+
+        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+        public void onResume() {}
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingApi() {
+        new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
+    }
+}
diff --git a/lifecycle/runtime/src/main/AndroidManifest.xml b/lifecycle/runtime/src/main/AndroidManifest.xml
index 274a076..d0df60e 100644
--- a/lifecycle/runtime/src/main/AndroidManifest.xml
+++ b/lifecycle/runtime/src/main/AndroidManifest.xml
@@ -16,5 +16,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle">
+          package="androidx.lifecycle">
 </manifest>
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
deleted file mode 100644
index eff946b..0000000
--- a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistry.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.Lifecycle.State.CREATED;
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-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 android.arch.core.internal.FastSafeIterableMap;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Map.Entry;
-
-/**
- * An implementation of {@link Lifecycle} that can handle multiple observers.
- * <p>
- * It is used by Fragments and Support Library Activities. You can also directly use it if you have
- * a custom LifecycleOwner.
- */
-public class LifecycleRegistry extends Lifecycle {
-
-    private static final String LOG_TAG = "LifecycleRegistry";
-
-    /**
-     * Custom list that keeps observers and can handle removals / additions during traversal.
-     *
-     * Invariant: at any moment of time for observer1 & observer2:
-     * if addition_order(observer1) < addition_order(observer2), then
-     * state(observer1) >= state(observer2),
-     */
-    private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =
-            new FastSafeIterableMap<>();
-    /**
-     * Current state
-     */
-    private State mState;
-    /**
-     * The provider that owns this Lifecycle.
-     * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
-     * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
-     * because it keeps strong references on all other listeners, so you'll leak all of them as
-     * well.
-     */
-    private final WeakReference<LifecycleOwner> mLifecycleOwner;
-
-    private int mAddingObserverCounter = 0;
-
-    private boolean mHandlingEvent = false;
-    private boolean mNewEventOccurred = false;
-
-    // we have to keep it for cases:
-    // void onStart() {
-    //     mRegistry.removeObserver(this);
-    //     mRegistry.add(newObserver);
-    // }
-    // newObserver should be brought only to CREATED state during the execution of
-    // this onStart method. our invariant with mObserverMap doesn't help, because parent observer
-    // is no longer in the map.
-    private ArrayList<State> mParentStates = new ArrayList<>();
-
-    /**
-     * Creates a new LifecycleRegistry for the given provider.
-     * <p>
-     * You should usually create this inside your LifecycleOwner class's constructor and hold
-     * onto the same instance.
-     *
-     * @param provider The owner LifecycleOwner
-     */
-    public LifecycleRegistry(@NonNull LifecycleOwner provider) {
-        mLifecycleOwner = new WeakReference<>(provider);
-        mState = INITIALIZED;
-    }
-
-    /**
-     * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
-     *
-     * @param state new state
-     */
-    @SuppressWarnings("WeakerAccess")
-    @MainThread
-    public void markState(@NonNull State state) {
-        moveToState(state);
-    }
-
-    /**
-     * Sets the current state and notifies the observers.
-     * <p>
-     * Note that if the {@code currentState} is the same state as the last call to this method,
-     * calling this method has no effect.
-     *
-     * @param event The event that was received
-     */
-    public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
-        State next = getStateAfter(event);
-        moveToState(next);
-    }
-
-    private void moveToState(State next) {
-        if (mState == next) {
-            return;
-        }
-        mState = next;
-        if (mHandlingEvent || mAddingObserverCounter != 0) {
-            mNewEventOccurred = true;
-            // we will figure out what to do on upper level.
-            return;
-        }
-        mHandlingEvent = true;
-        sync();
-        mHandlingEvent = false;
-    }
-
-    private boolean isSynced() {
-        if (mObserverMap.size() == 0) {
-            return true;
-        }
-        State eldestObserverState = mObserverMap.eldest().getValue().mState;
-        State newestObserverState = mObserverMap.newest().getValue().mState;
-        return eldestObserverState == newestObserverState && mState == newestObserverState;
-    }
-
-    private State calculateTargetState(LifecycleObserver observer) {
-        Entry<LifecycleObserver, ObserverWithState> previous = mObserverMap.ceil(observer);
-
-        State siblingState = previous != null ? previous.getValue().mState : null;
-        State parentState = !mParentStates.isEmpty() ? mParentStates.get(mParentStates.size() - 1)
-                : null;
-        return min(min(mState, siblingState), parentState);
-    }
-
-    @Override
-    public void addObserver(@NonNull LifecycleObserver observer) {
-        State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
-        ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
-        ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
-
-        if (previous != null) {
-            return;
-        }
-        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
-        if (lifecycleOwner == null) {
-            // it is null we should be destroyed. Fallback quickly
-            return;
-        }
-
-        boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
-        State targetState = calculateTargetState(observer);
-        mAddingObserverCounter++;
-        while ((statefulObserver.mState.compareTo(targetState) < 0
-                && mObserverMap.contains(observer))) {
-            pushParentState(statefulObserver.mState);
-            statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
-            popParentState();
-            // mState / subling may have been changed recalculate
-            targetState = calculateTargetState(observer);
-        }
-
-        if (!isReentrance) {
-            // we do sync only on the top level.
-            sync();
-        }
-        mAddingObserverCounter--;
-    }
-
-    private void popParentState() {
-        mParentStates.remove(mParentStates.size() - 1);
-    }
-
-    private void pushParentState(State state) {
-        mParentStates.add(state);
-    }
-
-    @Override
-    public void removeObserver(@NonNull LifecycleObserver observer) {
-        // we consciously decided not to send destruction events here in opposition to addObserver.
-        // Our reasons for that:
-        // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
-        // actually occurred but earlier.
-        // 2. There are cases when removeObserver happens as a consequence of some kind of fatal
-        // event. If removeObserver method sends destruction events, then a clean up routine becomes
-        // more cumbersome. More specific example of that is: your LifecycleObserver listens for
-        // a web connection, in the usual routine in OnStop method you report to a server that a
-        // session has just ended and you close the connection. Now let's assume now that you
-        // lost an internet and as a result you removed this observer. If you get destruction
-        // events in removeObserver, you should have a special case in your onStop method that
-        // checks if your web connection died and you shouldn't try to report anything to a server.
-        mObserverMap.remove(observer);
-    }
-
-    /**
-     * The number of observers.
-     *
-     * @return The number of observers.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public int getObserverCount() {
-        return mObserverMap.size();
-    }
-
-    @NonNull
-    @Override
-    public State getCurrentState() {
-        return mState;
-    }
-
-    static State getStateAfter(Event event) {
-        switch (event) {
-            case ON_CREATE:
-            case ON_STOP:
-                return CREATED;
-            case ON_START:
-            case ON_PAUSE:
-                return STARTED;
-            case ON_RESUME:
-                return RESUMED;
-            case ON_DESTROY:
-                return DESTROYED;
-            case ON_ANY:
-                break;
-        }
-        throw new IllegalArgumentException("Unexpected event value " + event);
-    }
-
-    private static Event downEvent(State state) {
-        switch (state) {
-            case INITIALIZED:
-                throw new IllegalArgumentException();
-            case CREATED:
-                return ON_DESTROY;
-            case STARTED:
-                return ON_STOP;
-            case RESUMED:
-                return ON_PAUSE;
-            case DESTROYED:
-                throw new IllegalArgumentException();
-        }
-        throw new IllegalArgumentException("Unexpected state value " + state);
-    }
-
-    private static Event upEvent(State state) {
-        switch (state) {
-            case INITIALIZED:
-            case DESTROYED:
-                return ON_CREATE;
-            case CREATED:
-                return ON_START;
-            case STARTED:
-                return ON_RESUME;
-            case RESUMED:
-                throw new IllegalArgumentException();
-        }
-        throw new IllegalArgumentException("Unexpected state value " + state);
-    }
-
-    private void forwardPass(LifecycleOwner lifecycleOwner) {
-        Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
-                mObserverMap.iteratorWithAdditions();
-        while (ascendingIterator.hasNext() && !mNewEventOccurred) {
-            Entry<LifecycleObserver, ObserverWithState> entry = ascendingIterator.next();
-            ObserverWithState observer = entry.getValue();
-            while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
-                    && mObserverMap.contains(entry.getKey()))) {
-                pushParentState(observer.mState);
-                observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
-                popParentState();
-            }
-        }
-    }
-
-    private void backwardPass(LifecycleOwner lifecycleOwner) {
-        Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
-                mObserverMap.descendingIterator();
-        while (descendingIterator.hasNext() && !mNewEventOccurred) {
-            Entry<LifecycleObserver, ObserverWithState> entry = descendingIterator.next();
-            ObserverWithState observer = entry.getValue();
-            while ((observer.mState.compareTo(mState) > 0 && !mNewEventOccurred
-                    && mObserverMap.contains(entry.getKey()))) {
-                Event event = downEvent(observer.mState);
-                pushParentState(getStateAfter(event));
-                observer.dispatchEvent(lifecycleOwner, event);
-                popParentState();
-            }
-        }
-    }
-
-    // happens only on the top of stack (never in reentrance),
-    // so it doesn't have to take in account parents
-    private void sync() {
-        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
-        if (lifecycleOwner == null) {
-            Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
-                    + "new events from it.");
-            return;
-        }
-        while (!isSynced()) {
-            mNewEventOccurred = false;
-            // no need to check eldest for nullability, because isSynced does it for us.
-            if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
-                backwardPass(lifecycleOwner);
-            }
-            Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
-            if (!mNewEventOccurred && newest != null
-                    && mState.compareTo(newest.getValue().mState) > 0) {
-                forwardPass(lifecycleOwner);
-            }
-        }
-        mNewEventOccurred = false;
-    }
-
-    static State min(@NonNull State state1, @Nullable State state2) {
-        return state2 != null && state2.compareTo(state1) < 0 ? state2 : state1;
-    }
-
-    static class ObserverWithState {
-        State mState;
-        GenericLifecycleObserver mLifecycleObserver;
-
-        ObserverWithState(LifecycleObserver observer, State initialState) {
-            mLifecycleObserver = Lifecycling.getCallback(observer);
-            mState = initialState;
-        }
-
-        void dispatchEvent(LifecycleOwner owner, Event event) {
-            State newState = getStateAfter(event);
-            mState = min(mState, newState);
-            mLifecycleObserver.onStateChanged(owner, event);
-            mState = newState;
-        }
-    }
-}
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java
deleted file mode 100644
index 0c67fef..0000000
--- a/lifecycle/runtime/src/main/java/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.annotation.NonNull;
-
-/**
- * @deprecated Use {@code android.support.v7.app.AppCompatActivity}
- * which extends {@link LifecycleOwner}, so there are no use cases for this class.
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-@Deprecated
-public interface LifecycleRegistryOwner extends LifecycleOwner {
-    @NonNull
-    @Override
-    LifecycleRegistry getLifecycle();
-}
diff --git a/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java b/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java
deleted file mode 100644
index 16a89ce..0000000
--- a/lifecycle/runtime/src/main/java/android/arch/lifecycle/ReportFragment.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.annotation.RestrictTo;
-
-/**
- * Internal class that dispatches initialization events.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ReportFragment extends Fragment {
-    private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
-            + ".LifecycleDispatcher.report_fragment_tag";
-
-    public static void injectIfNeededIn(Activity activity) {
-        // ProcessLifecycleOwner should always correctly work and some activities may not extend
-        // FragmentActivity from support lib, so we use framework fragments for activities
-        android.app.FragmentManager manager = activity.getFragmentManager();
-        if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
-            manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
-            // Hopefully, we are the first to make a transaction.
-            manager.executePendingTransactions();
-        }
-    }
-
-    static ReportFragment get(Activity activity) {
-        return (ReportFragment) activity.getFragmentManager().findFragmentByTag(
-                REPORT_FRAGMENT_TAG);
-    }
-
-    private ActivityInitializationListener mProcessListener;
-
-    private void dispatchCreate(ActivityInitializationListener listener) {
-        if (listener != null) {
-            listener.onCreate();
-        }
-    }
-
-    private void dispatchStart(ActivityInitializationListener listener) {
-        if (listener != null) {
-            listener.onStart();
-        }
-    }
-
-    private void dispatchResume(ActivityInitializationListener listener) {
-        if (listener != null) {
-            listener.onResume();
-        }
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        dispatchCreate(mProcessListener);
-        dispatch(Lifecycle.Event.ON_CREATE);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        dispatchStart(mProcessListener);
-        dispatch(Lifecycle.Event.ON_START);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        dispatchResume(mProcessListener);
-        dispatch(Lifecycle.Event.ON_RESUME);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        dispatch(Lifecycle.Event.ON_PAUSE);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        dispatch(Lifecycle.Event.ON_STOP);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        dispatch(Lifecycle.Event.ON_DESTROY);
-        // just want to be sure that we won't leak reference to an activity
-        mProcessListener = null;
-    }
-
-    private void dispatch(Lifecycle.Event event) {
-        Activity activity = getActivity();
-        if (activity instanceof LifecycleRegistryOwner) {
-            ((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event);
-            return;
-        }
-
-        if (activity instanceof LifecycleOwner) {
-            Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();
-            if (lifecycle instanceof LifecycleRegistry) {
-                ((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);
-            }
-        }
-    }
-
-    void setProcessListener(ActivityInitializationListener processListener) {
-        mProcessListener = processListener;
-    }
-
-    interface ActivityInitializationListener {
-        void onCreate();
-
-        void onStart();
-
-        void onResume();
-    }
-}
diff --git a/lifecycle/runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java b/lifecycle/runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java
new file mode 100644
index 0000000..3dcc2bb
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java
@@ -0,0 +1,359 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static androidx.lifecycle.Lifecycle.State.CREATED;
+import static androidx.lifecycle.Lifecycle.State.DESTROYED;
+import static androidx.lifecycle.Lifecycle.State.INITIALIZED;
+import static androidx.lifecycle.Lifecycle.State.RESUMED;
+import static androidx.lifecycle.Lifecycle.State.STARTED;
+
+import android.util.Log;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.internal.FastSafeIterableMap;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+/**
+ * An implementation of {@link Lifecycle} that can handle multiple observers.
+ * <p>
+ * It is used by Fragments and Support Library Activities. You can also directly use it if you have
+ * a custom LifecycleOwner.
+ */
+public class LifecycleRegistry extends Lifecycle {
+
+    private static final String LOG_TAG = "LifecycleRegistry";
+
+    /**
+     * Custom list that keeps observers and can handle removals / additions during traversal.
+     *
+     * Invariant: at any moment of time for observer1 & observer2:
+     * if addition_order(observer1) < addition_order(observer2), then
+     * state(observer1) >= state(observer2),
+     */
+    private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =
+            new FastSafeIterableMap<>();
+    /**
+     * Current state
+     */
+    private State mState;
+    /**
+     * The provider that owns this Lifecycle.
+     * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
+     * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
+     * because it keeps strong references on all other listeners, so you'll leak all of them as
+     * well.
+     */
+    private final WeakReference<LifecycleOwner> mLifecycleOwner;
+
+    private int mAddingObserverCounter = 0;
+
+    private boolean mHandlingEvent = false;
+    private boolean mNewEventOccurred = false;
+
+    // we have to keep it for cases:
+    // void onStart() {
+    //     mRegistry.removeObserver(this);
+    //     mRegistry.add(newObserver);
+    // }
+    // newObserver should be brought only to CREATED state during the execution of
+    // this onStart method. our invariant with mObserverMap doesn't help, because parent observer
+    // is no longer in the map.
+    private ArrayList<State> mParentStates = new ArrayList<>();
+
+    /**
+     * Creates a new LifecycleRegistry for the given provider.
+     * <p>
+     * You should usually create this inside your LifecycleOwner class's constructor and hold
+     * onto the same instance.
+     *
+     * @param provider The owner LifecycleOwner
+     */
+    public LifecycleRegistry(@NonNull LifecycleOwner provider) {
+        mLifecycleOwner = new WeakReference<>(provider);
+        mState = INITIALIZED;
+    }
+
+    /**
+     * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
+     *
+     * @param state new state
+     */
+    @SuppressWarnings("WeakerAccess")
+    @MainThread
+    public void markState(@NonNull State state) {
+        moveToState(state);
+    }
+
+    /**
+     * Sets the current state and notifies the observers.
+     * <p>
+     * Note that if the {@code currentState} is the same state as the last call to this method,
+     * calling this method has no effect.
+     *
+     * @param event The event that was received
+     */
+    public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
+        State next = getStateAfter(event);
+        moveToState(next);
+    }
+
+    private void moveToState(State next) {
+        if (mState == next) {
+            return;
+        }
+        mState = next;
+        if (mHandlingEvent || mAddingObserverCounter != 0) {
+            mNewEventOccurred = true;
+            // we will figure out what to do on upper level.
+            return;
+        }
+        mHandlingEvent = true;
+        sync();
+        mHandlingEvent = false;
+    }
+
+    private boolean isSynced() {
+        if (mObserverMap.size() == 0) {
+            return true;
+        }
+        State eldestObserverState = mObserverMap.eldest().getValue().mState;
+        State newestObserverState = mObserverMap.newest().getValue().mState;
+        return eldestObserverState == newestObserverState && mState == newestObserverState;
+    }
+
+    private State calculateTargetState(LifecycleObserver observer) {
+        Entry<LifecycleObserver, ObserverWithState> previous = mObserverMap.ceil(observer);
+
+        State siblingState = previous != null ? previous.getValue().mState : null;
+        State parentState = !mParentStates.isEmpty() ? mParentStates.get(mParentStates.size() - 1)
+                : null;
+        return min(min(mState, siblingState), parentState);
+    }
+
+    @Override
+    public void addObserver(@NonNull LifecycleObserver observer) {
+        State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
+        ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
+        ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
+
+        if (previous != null) {
+            return;
+        }
+        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+        if (lifecycleOwner == null) {
+            // it is null we should be destroyed. Fallback quickly
+            return;
+        }
+
+        boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
+        State targetState = calculateTargetState(observer);
+        mAddingObserverCounter++;
+        while ((statefulObserver.mState.compareTo(targetState) < 0
+                && mObserverMap.contains(observer))) {
+            pushParentState(statefulObserver.mState);
+            statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
+            popParentState();
+            // mState / subling may have been changed recalculate
+            targetState = calculateTargetState(observer);
+        }
+
+        if (!isReentrance) {
+            // we do sync only on the top level.
+            sync();
+        }
+        mAddingObserverCounter--;
+    }
+
+    private void popParentState() {
+        mParentStates.remove(mParentStates.size() - 1);
+    }
+
+    private void pushParentState(State state) {
+        mParentStates.add(state);
+    }
+
+    @Override
+    public void removeObserver(@NonNull LifecycleObserver observer) {
+        // we consciously decided not to send destruction events here in opposition to addObserver.
+        // Our reasons for that:
+        // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
+        // actually occurred but earlier.
+        // 2. There are cases when removeObserver happens as a consequence of some kind of fatal
+        // event. If removeObserver method sends destruction events, then a clean up routine becomes
+        // more cumbersome. More specific example of that is: your LifecycleObserver listens for
+        // a web connection, in the usual routine in OnStop method you report to a server that a
+        // session has just ended and you close the connection. Now let's assume now that you
+        // lost an internet and as a result you removed this observer. If you get destruction
+        // events in removeObserver, you should have a special case in your onStop method that
+        // checks if your web connection died and you shouldn't try to report anything to a server.
+        mObserverMap.remove(observer);
+    }
+
+    /**
+     * The number of observers.
+     *
+     * @return The number of observers.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public int getObserverCount() {
+        return mObserverMap.size();
+    }
+
+    @NonNull
+    @Override
+    public State getCurrentState() {
+        return mState;
+    }
+
+    static State getStateAfter(Event event) {
+        switch (event) {
+            case ON_CREATE:
+            case ON_STOP:
+                return CREATED;
+            case ON_START:
+            case ON_PAUSE:
+                return STARTED;
+            case ON_RESUME:
+                return RESUMED;
+            case ON_DESTROY:
+                return DESTROYED;
+            case ON_ANY:
+                break;
+        }
+        throw new IllegalArgumentException("Unexpected event value " + event);
+    }
+
+    private static Event downEvent(State state) {
+        switch (state) {
+            case INITIALIZED:
+                throw new IllegalArgumentException();
+            case CREATED:
+                return ON_DESTROY;
+            case STARTED:
+                return ON_STOP;
+            case RESUMED:
+                return ON_PAUSE;
+            case DESTROYED:
+                throw new IllegalArgumentException();
+        }
+        throw new IllegalArgumentException("Unexpected state value " + state);
+    }
+
+    private static Event upEvent(State state) {
+        switch (state) {
+            case INITIALIZED:
+            case DESTROYED:
+                return ON_CREATE;
+            case CREATED:
+                return ON_START;
+            case STARTED:
+                return ON_RESUME;
+            case RESUMED:
+                throw new IllegalArgumentException();
+        }
+        throw new IllegalArgumentException("Unexpected state value " + state);
+    }
+
+    private void forwardPass(LifecycleOwner lifecycleOwner) {
+        Iterator<Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
+                mObserverMap.iteratorWithAdditions();
+        while (ascendingIterator.hasNext() && !mNewEventOccurred) {
+            Entry<LifecycleObserver, ObserverWithState> entry = ascendingIterator.next();
+            ObserverWithState observer = entry.getValue();
+            while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
+                    && mObserverMap.contains(entry.getKey()))) {
+                pushParentState(observer.mState);
+                observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
+                popParentState();
+            }
+        }
+    }
+
+    private void backwardPass(LifecycleOwner lifecycleOwner) {
+        Iterator<Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
+                mObserverMap.descendingIterator();
+        while (descendingIterator.hasNext() && !mNewEventOccurred) {
+            Entry<LifecycleObserver, ObserverWithState> entry = descendingIterator.next();
+            ObserverWithState observer = entry.getValue();
+            while ((observer.mState.compareTo(mState) > 0 && !mNewEventOccurred
+                    && mObserverMap.contains(entry.getKey()))) {
+                Event event = downEvent(observer.mState);
+                pushParentState(getStateAfter(event));
+                observer.dispatchEvent(lifecycleOwner, event);
+                popParentState();
+            }
+        }
+    }
+
+    // happens only on the top of stack (never in reentrance),
+    // so it doesn't have to take in account parents
+    private void sync() {
+        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
+        if (lifecycleOwner == null) {
+            Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
+                    + "new events from it.");
+            return;
+        }
+        while (!isSynced()) {
+            mNewEventOccurred = false;
+            // no need to check eldest for nullability, because isSynced does it for us.
+            if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
+                backwardPass(lifecycleOwner);
+            }
+            Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
+            if (!mNewEventOccurred && newest != null
+                    && mState.compareTo(newest.getValue().mState) > 0) {
+                forwardPass(lifecycleOwner);
+            }
+        }
+        mNewEventOccurred = false;
+    }
+
+    static State min(@NonNull State state1, @Nullable State state2) {
+        return state2 != null && state2.compareTo(state1) < 0 ? state2 : state1;
+    }
+
+    static class ObserverWithState {
+        State mState;
+        GenericLifecycleObserver mLifecycleObserver;
+
+        ObserverWithState(LifecycleObserver observer, State initialState) {
+            mLifecycleObserver = Lifecycling.getCallback(observer);
+            mState = initialState;
+        }
+
+        void dispatchEvent(LifecycleOwner owner, Event event) {
+            State newState = getStateAfter(event);
+            mState = min(mState, newState);
+            mLifecycleObserver.onStateChanged(owner, event);
+            mState = newState;
+        }
+    }
+}
diff --git a/lifecycle/runtime/src/main/java/androidx/lifecycle/LifecycleRegistryOwner.java b/lifecycle/runtime/src/main/java/androidx/lifecycle/LifecycleRegistryOwner.java
new file mode 100644
index 0000000..630b98b
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/androidx/lifecycle/LifecycleRegistryOwner.java
@@ -0,0 +1,31 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.NonNull;
+
+/**
+ * @deprecated Use {@code android.support.v7.app.AppCompatActivity}
+ * which extends {@link LifecycleOwner}, so there are no use cases for this class.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+@Deprecated
+public interface LifecycleRegistryOwner extends LifecycleOwner {
+    @NonNull
+    @Override
+    LifecycleRegistry getLifecycle();
+}
diff --git a/lifecycle/runtime/src/main/java/androidx/lifecycle/ReportFragment.java b/lifecycle/runtime/src/main/java/androidx/lifecycle/ReportFragment.java
new file mode 100644
index 0000000..33c7771
--- /dev/null
+++ b/lifecycle/runtime/src/main/java/androidx/lifecycle/ReportFragment.java
@@ -0,0 +1,138 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Internal class that dispatches initialization events.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ReportFragment extends Fragment {
+    private static final String REPORT_FRAGMENT_TAG = "androidx.lifecycle"
+            + ".LifecycleDispatcher.report_fragment_tag";
+
+    public static void injectIfNeededIn(Activity activity) {
+        // ProcessLifecycleOwner should always correctly work and some activities may not extend
+        // FragmentActivity from support lib, so we use framework fragments for activities
+        android.app.FragmentManager manager = activity.getFragmentManager();
+        if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
+            manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
+            // Hopefully, we are the first to make a transaction.
+            manager.executePendingTransactions();
+        }
+    }
+
+    static ReportFragment get(Activity activity) {
+        return (ReportFragment) activity.getFragmentManager().findFragmentByTag(
+                REPORT_FRAGMENT_TAG);
+    }
+
+    private ActivityInitializationListener mProcessListener;
+
+    private void dispatchCreate(ActivityInitializationListener listener) {
+        if (listener != null) {
+            listener.onCreate();
+        }
+    }
+
+    private void dispatchStart(ActivityInitializationListener listener) {
+        if (listener != null) {
+            listener.onStart();
+        }
+    }
+
+    private void dispatchResume(ActivityInitializationListener listener) {
+        if (listener != null) {
+            listener.onResume();
+        }
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        dispatchCreate(mProcessListener);
+        dispatch(Lifecycle.Event.ON_CREATE);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        dispatchStart(mProcessListener);
+        dispatch(Lifecycle.Event.ON_START);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        dispatchResume(mProcessListener);
+        dispatch(Lifecycle.Event.ON_RESUME);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        dispatch(Lifecycle.Event.ON_PAUSE);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        dispatch(Lifecycle.Event.ON_STOP);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        dispatch(Lifecycle.Event.ON_DESTROY);
+        // just want to be sure that we won't leak reference to an activity
+        mProcessListener = null;
+    }
+
+    private void dispatch(Lifecycle.Event event) {
+        Activity activity = getActivity();
+        if (activity instanceof LifecycleRegistryOwner) {
+            ((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event);
+            return;
+        }
+
+        if (activity instanceof LifecycleOwner) {
+            Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();
+            if (lifecycle instanceof LifecycleRegistry) {
+                ((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);
+            }
+        }
+    }
+
+    void setProcessListener(ActivityInitializationListener processListener) {
+        mProcessListener = processListener;
+    }
+
+    interface ActivityInitializationListener {
+        void onCreate();
+
+        void onStart();
+
+        void onResume();
+    }
+}
diff --git a/lifecycle/runtime/src/test/java/NoPackageObserver.java b/lifecycle/runtime/src/test/java/NoPackageObserver.java
index 58f9fba..7452d7c 100644
--- a/lifecycle/runtime/src/test/java/NoPackageObserver.java
+++ b/lifecycle/runtime/src/test/java/NoPackageObserver.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
 
-import android.arch.lifecycle.LifecycleObserver;
-import android.arch.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
 
 class NoPackageObserver implements LifecycleObserver {
     @OnLifecycleEvent(ON_CREATE)
diff --git a/lifecycle/runtime/src/test/java/NoPackageTest.java b/lifecycle/runtime/src/test/java/NoPackageTest.java
index 8763202..e84e87d 100644
--- a/lifecycle/runtime/src/test/java/NoPackageTest.java
+++ b/lifecycle/runtime/src/test/java/NoPackageTest.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java b/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java
deleted file mode 100644
index 2a7bbad..0000000
--- a/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java
+++ /dev/null
@@ -1,624 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.InOrder;
-
-@RunWith(JUnit4.class)
-public class LifecycleRegistryTest {
-    private LifecycleOwner mLifecycleOwner;
-    private Lifecycle mLifecycle;
-    private LifecycleRegistry mRegistry;
-
-    @Before
-    public void init() {
-        mLifecycleOwner = mock(LifecycleOwner.class);
-        mLifecycle = mock(Lifecycle.class);
-        when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle);
-        mRegistry = new LifecycleRegistry(mLifecycleOwner);
-    }
-
-    @Test
-    public void addRemove() {
-        LifecycleObserver observer = mock(LifecycleObserver.class);
-        mRegistry.addObserver(observer);
-        assertThat(mRegistry.getObserverCount(), is(1));
-        mRegistry.removeObserver(observer);
-        assertThat(mRegistry.getObserverCount(), is(0));
-    }
-
-    @Test
-    public void addGenericAndObserve() {
-        GenericLifecycleObserver generic = mock(GenericLifecycleObserver.class);
-        mRegistry.addObserver(generic);
-        dispatchEvent(ON_CREATE);
-        verify(generic).onStateChanged(mLifecycleOwner, ON_CREATE);
-        reset(generic);
-        dispatchEvent(ON_CREATE);
-        verify(generic, never()).onStateChanged(mLifecycleOwner, ON_CREATE);
-    }
-
-    @Test
-    public void addRegularClass() {
-        TestObserver testObserver = mock(TestObserver.class);
-        mRegistry.addObserver(testObserver);
-        dispatchEvent(ON_START);
-        verify(testObserver, never()).onStop();
-        dispatchEvent(ON_STOP);
-        verify(testObserver).onStop();
-    }
-
-    @Test
-    public void add2RemoveOne() {
-        TestObserver observer1 = mock(TestObserver.class);
-        TestObserver observer2 = mock(TestObserver.class);
-        TestObserver observer3 = mock(TestObserver.class);
-        mRegistry.addObserver(observer1);
-        mRegistry.addObserver(observer2);
-        mRegistry.addObserver(observer3);
-
-        dispatchEvent(ON_CREATE);
-
-        verify(observer1).onCreate();
-        verify(observer2).onCreate();
-        verify(observer3).onCreate();
-        reset(observer1, observer2, observer3);
-
-        mRegistry.removeObserver(observer2);
-        dispatchEvent(ON_START);
-
-        verify(observer1).onStart();
-        verify(observer2, never()).onStart();
-        verify(observer3).onStart();
-    }
-
-    @Test
-    public void removeWhileTraversing() {
-        final TestObserver observer2 = mock(TestObserver.class);
-        TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            public void onCreate() {
-                mRegistry.removeObserver(observer2);
-            }
-        });
-        mRegistry.addObserver(observer1);
-        mRegistry.addObserver(observer2);
-        dispatchEvent(ON_CREATE);
-        verify(observer2, never()).onCreate();
-        verify(observer1).onCreate();
-    }
-
-    @Test
-    public void constructionOrder() {
-        fullyInitializeRegistry();
-        final TestObserver observer = mock(TestObserver.class);
-        mRegistry.addObserver(observer);
-        InOrder inOrder = inOrder(observer);
-        inOrder.verify(observer).onCreate();
-        inOrder.verify(observer).onStart();
-        inOrder.verify(observer).onResume();
-    }
-
-    @Test
-    public void constructionDestruction1() {
-        fullyInitializeRegistry();
-        final TestObserver observer = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                dispatchEvent(ON_PAUSE);
-            }
-        });
-        mRegistry.addObserver(observer);
-        InOrder constructionOrder = inOrder(observer);
-        constructionOrder.verify(observer).onCreate();
-        constructionOrder.verify(observer).onStart();
-        constructionOrder.verify(observer, never()).onResume();
-    }
-
-    @Test
-    public void constructionDestruction2() {
-        fullyInitializeRegistry();
-        final TestObserver observer = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                dispatchEvent(ON_PAUSE);
-                dispatchEvent(ON_STOP);
-                dispatchEvent(ON_DESTROY);
-            }
-        });
-        mRegistry.addObserver(observer);
-        InOrder orderVerifier = inOrder(observer);
-        orderVerifier.verify(observer).onCreate();
-        orderVerifier.verify(observer).onStart();
-        orderVerifier.verify(observer).onStop();
-        orderVerifier.verify(observer).onDestroy();
-        orderVerifier.verify(observer, never()).onResume();
-    }
-
-    @Test
-    public void twoObserversChangingState() {
-        final TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onCreate() {
-                dispatchEvent(ON_START);
-            }
-        });
-        final TestObserver observer2 = mock(TestObserver.class);
-        mRegistry.addObserver(observer1);
-        mRegistry.addObserver(observer2);
-        dispatchEvent(ON_CREATE);
-        verify(observer1, times(1)).onCreate();
-        verify(observer2, times(1)).onCreate();
-        verify(observer1, times(1)).onStart();
-        verify(observer2, times(1)).onStart();
-    }
-
-    @Test
-    public void addDuringTraversing() {
-        final TestObserver observer3 = mock(TestObserver.class);
-        final TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            public void onStart() {
-                mRegistry.addObserver(observer3);
-            }
-        });
-        final TestObserver observer2 = mock(TestObserver.class);
-
-        mRegistry.addObserver(observer1);
-        mRegistry.addObserver(observer2);
-
-        dispatchEvent(ON_CREATE);
-        dispatchEvent(ON_START);
-
-        InOrder inOrder = inOrder(observer1, observer2, observer3);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer2).onStart();
-        inOrder.verify(observer3).onStart();
-    }
-
-    @Test
-    public void addDuringAddition() {
-        final TestObserver observer3 = mock(TestObserver.class);
-        final TestObserver observer2 = spy(new TestObserver() {
-            @Override
-            public void onCreate() {
-                mRegistry.addObserver(observer3);
-            }
-        });
-
-        final TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            public void onResume() {
-                mRegistry.addObserver(observer2);
-            }
-        });
-
-        mRegistry.addObserver(observer1);
-
-        dispatchEvent(ON_CREATE);
-        dispatchEvent(ON_START);
-        dispatchEvent(ON_RESUME);
-
-        InOrder inOrder = inOrder(observer1, observer2, observer3);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer1).onResume();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer2).onStart();
-        inOrder.verify(observer2).onResume();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer3).onStart();
-        inOrder.verify(observer3).onResume();
-    }
-
-    @Test
-    public void subscribeToDead() {
-        dispatchEvent(ON_CREATE);
-        final TestObserver observer1 = mock(TestObserver.class);
-        mRegistry.addObserver(observer1);
-        verify(observer1).onCreate();
-        dispatchEvent(ON_DESTROY);
-        verify(observer1).onDestroy();
-        final TestObserver observer2 = mock(TestObserver.class);
-        mRegistry.addObserver(observer2);
-        verify(observer2, never()).onCreate();
-        reset(observer1);
-        dispatchEvent(ON_CREATE);
-        verify(observer1).onCreate();
-        verify(observer2).onCreate();
-    }
-
-    @Test
-    public void downEvents() {
-        fullyInitializeRegistry();
-        final TestObserver observer1 = mock(TestObserver.class);
-        final TestObserver observer2 = mock(TestObserver.class);
-        mRegistry.addObserver(observer1);
-        mRegistry.addObserver(observer2);
-        InOrder orderVerifier = inOrder(observer1, observer2);
-        dispatchEvent(ON_PAUSE);
-        orderVerifier.verify(observer2).onPause();
-        orderVerifier.verify(observer1).onPause();
-        dispatchEvent(ON_STOP);
-        orderVerifier.verify(observer2).onStop();
-        orderVerifier.verify(observer1).onStop();
-        dispatchEvent(ON_DESTROY);
-        orderVerifier.verify(observer2).onDestroy();
-        orderVerifier.verify(observer1).onDestroy();
-    }
-
-    @Test
-    public void downEventsAddition() {
-        dispatchEvent(ON_CREATE);
-        dispatchEvent(ON_START);
-        final TestObserver observer1 = mock(TestObserver.class);
-        final TestObserver observer3 = mock(TestObserver.class);
-        final TestObserver observer2 = spy(new TestObserver() {
-            @Override
-            void onStop() {
-                mRegistry.addObserver(observer3);
-            }
-        });
-        mRegistry.addObserver(observer1);
-        mRegistry.addObserver(observer2);
-        InOrder orderVerifier = inOrder(observer1, observer2, observer3);
-        dispatchEvent(ON_STOP);
-        orderVerifier.verify(observer2).onStop();
-        orderVerifier.verify(observer3).onCreate();
-        orderVerifier.verify(observer1).onStop();
-        dispatchEvent(ON_DESTROY);
-        orderVerifier.verify(observer3).onDestroy();
-        orderVerifier.verify(observer2).onDestroy();
-        orderVerifier.verify(observer1).onDestroy();
-    }
-
-    @Test
-    public void downEventsRemoveAll() {
-        fullyInitializeRegistry();
-        final TestObserver observer1 = mock(TestObserver.class);
-        final TestObserver observer3 = mock(TestObserver.class);
-        final TestObserver observer2 = spy(new TestObserver() {
-            @Override
-            void onStop() {
-                mRegistry.removeObserver(observer3);
-                mRegistry.removeObserver(this);
-                mRegistry.removeObserver(observer1);
-                assertThat(mRegistry.getObserverCount(), is(0));
-            }
-        });
-        mRegistry.addObserver(observer1);
-        mRegistry.addObserver(observer2);
-        mRegistry.addObserver(observer3);
-        InOrder orderVerifier = inOrder(observer1, observer2, observer3);
-        dispatchEvent(ON_PAUSE);
-        orderVerifier.verify(observer3).onPause();
-        orderVerifier.verify(observer2).onPause();
-        orderVerifier.verify(observer1).onPause();
-        dispatchEvent(ON_STOP);
-        orderVerifier.verify(observer3).onStop();
-        orderVerifier.verify(observer2).onStop();
-        orderVerifier.verify(observer1, never()).onStop();
-        dispatchEvent(ON_PAUSE);
-        orderVerifier.verify(observer3, never()).onPause();
-        orderVerifier.verify(observer2, never()).onPause();
-        orderVerifier.verify(observer1, never()).onPause();
-    }
-
-    @Test
-    public void deadParentInAddition() {
-        fullyInitializeRegistry();
-        final TestObserver observer2 = mock(TestObserver.class);
-        final TestObserver observer3 = mock(TestObserver.class);
-
-        TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                mRegistry.removeObserver(this);
-                assertThat(mRegistry.getObserverCount(), is(0));
-                mRegistry.addObserver(observer2);
-                mRegistry.addObserver(observer3);
-            }
-        });
-
-        mRegistry.addObserver(observer1);
-
-        InOrder inOrder = inOrder(observer1, observer2, observer3);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer2).onStart();
-        inOrder.verify(observer2).onResume();
-        inOrder.verify(observer3).onStart();
-        inOrder.verify(observer3).onResume();
-    }
-
-    @Test
-    public void deadParentWhileTraversing() {
-        final TestObserver observer2 = mock(TestObserver.class);
-        final TestObserver observer3 = mock(TestObserver.class);
-        TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                mRegistry.removeObserver(this);
-                assertThat(mRegistry.getObserverCount(), is(0));
-                mRegistry.addObserver(observer2);
-                mRegistry.addObserver(observer3);
-            }
-        });
-        InOrder inOrder = inOrder(observer1, observer2, observer3);
-        mRegistry.addObserver(observer1);
-        dispatchEvent(ON_CREATE);
-        dispatchEvent(ON_START);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer2).onStart();
-        inOrder.verify(observer3).onStart();
-    }
-
-    @Test
-    public void removeCascade() {
-        final TestObserver observer3 = mock(TestObserver.class);
-        final TestObserver observer4 = mock(TestObserver.class);
-
-        final TestObserver observer2 = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                mRegistry.removeObserver(this);
-            }
-        });
-
-        TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onResume() {
-                mRegistry.removeObserver(this);
-                mRegistry.addObserver(observer2);
-                mRegistry.addObserver(observer3);
-                mRegistry.addObserver(observer4);
-            }
-        });
-        fullyInitializeRegistry();
-        mRegistry.addObserver(observer1);
-        InOrder inOrder = inOrder(observer1, observer2, observer3, observer4);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer1).onResume();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer2).onStart();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer3).onStart();
-        inOrder.verify(observer4).onCreate();
-        inOrder.verify(observer4).onStart();
-        inOrder.verify(observer3).onResume();
-        inOrder.verify(observer4).onResume();
-    }
-
-    @Test
-    public void changeStateDuringDescending() {
-        final TestObserver observer2 = mock(TestObserver.class);
-        final TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onPause() {
-                // but tonight I bounce back
-                mRegistry.handleLifecycleEvent(ON_RESUME);
-                mRegistry.addObserver(observer2);
-            }
-        });
-        fullyInitializeRegistry();
-        mRegistry.addObserver(observer1);
-        mRegistry.handleLifecycleEvent(ON_PAUSE);
-        InOrder inOrder = inOrder(observer1, observer2);
-        inOrder.verify(observer1).onPause();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer2).onStart();
-        inOrder.verify(observer1).onResume();
-        inOrder.verify(observer2).onResume();
-    }
-
-    @Test
-    public void siblingLimitationCheck() {
-        fullyInitializeRegistry();
-        final TestObserver observer2 = mock(TestObserver.class);
-        final TestObserver observer3 = mock(TestObserver.class);
-        final TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                mRegistry.addObserver(observer2);
-            }
-
-            @Override
-            void onResume() {
-                mRegistry.addObserver(observer3);
-            }
-        });
-        mRegistry.addObserver(observer1);
-        InOrder inOrder = inOrder(observer1, observer2, observer3);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer1).onResume();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer2).onStart();
-        inOrder.verify(observer2).onResume();
-        inOrder.verify(observer3).onStart();
-        inOrder.verify(observer3).onResume();
-    }
-
-    @Test
-    public void siblingRemovalLimitationCheck1() {
-        fullyInitializeRegistry();
-        final TestObserver observer2 = mock(TestObserver.class);
-        final TestObserver observer3 = mock(TestObserver.class);
-        final TestObserver observer4 = mock(TestObserver.class);
-        final TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                mRegistry.addObserver(observer2);
-            }
-
-            @Override
-            void onResume() {
-                mRegistry.removeObserver(observer2);
-                mRegistry.addObserver(observer3);
-                mRegistry.addObserver(observer4);
-            }
-        });
-        mRegistry.addObserver(observer1);
-        InOrder inOrder = inOrder(observer1, observer2, observer3, observer4);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer1).onResume();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer3).onStart();
-        inOrder.verify(observer4).onCreate();
-        inOrder.verify(observer4).onStart();
-        inOrder.verify(observer3).onResume();
-        inOrder.verify(observer4).onResume();
-    }
-
-    @Test
-    public void siblingRemovalLimitationCheck2() {
-        fullyInitializeRegistry();
-        final TestObserver observer2 = mock(TestObserver.class);
-        final TestObserver observer3 = spy(new TestObserver() {
-            @Override
-            void onCreate() {
-                mRegistry.removeObserver(observer2);
-            }
-        });
-        final TestObserver observer4 = mock(TestObserver.class);
-        final TestObserver observer1 = spy(new TestObserver() {
-            @Override
-            void onStart() {
-                mRegistry.addObserver(observer2);
-            }
-
-            @Override
-            void onResume() {
-                mRegistry.addObserver(observer3);
-                mRegistry.addObserver(observer4);
-            }
-        });
-
-        mRegistry.addObserver(observer1);
-        InOrder inOrder = inOrder(observer1, observer2, observer3, observer4);
-        inOrder.verify(observer1).onCreate();
-        inOrder.verify(observer1).onStart();
-        inOrder.verify(observer2).onCreate();
-        inOrder.verify(observer1).onResume();
-        inOrder.verify(observer3).onCreate();
-        inOrder.verify(observer3).onStart();
-        inOrder.verify(observer4).onCreate();
-        inOrder.verify(observer4).onStart();
-        inOrder.verify(observer3).onResume();
-        inOrder.verify(observer4).onResume();
-    }
-
-    @Test
-    public void sameObserverReAddition() {
-        TestObserver observer = mock(TestObserver.class);
-        mRegistry.addObserver(observer);
-        mRegistry.removeObserver(observer);
-        mRegistry.addObserver(observer);
-        dispatchEvent(ON_CREATE);
-        verify(observer).onCreate();
-    }
-
-    private static void forceGc() {
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-    }
-
-    @Test
-    public void goneLifecycleOwner() {
-        fullyInitializeRegistry();
-        mLifecycleOwner = null;
-        forceGc();
-        TestObserver observer = mock(TestObserver.class);
-        mRegistry.addObserver(observer);
-        verify(observer, never()).onCreate();
-        verify(observer, never()).onStart();
-        verify(observer, never()).onResume();
-    }
-
-    private void dispatchEvent(Lifecycle.Event event) {
-        when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
-        mRegistry.handleLifecycleEvent(event);
-    }
-
-    private void fullyInitializeRegistry() {
-        dispatchEvent(ON_CREATE);
-        dispatchEvent(ON_START);
-        dispatchEvent(ON_RESUME);
-    }
-
-    private abstract class TestObserver implements LifecycleObserver {
-        @OnLifecycleEvent(ON_CREATE)
-        void onCreate() {
-        }
-
-        @OnLifecycleEvent(ON_START)
-        void onStart() {
-        }
-
-        @OnLifecycleEvent(ON_RESUME)
-        void onResume() {
-        }
-
-        @OnLifecycleEvent(ON_PAUSE)
-        void onPause() {
-        }
-
-        @OnLifecycleEvent(ON_STOP)
-        void onStop() {
-        }
-
-        @OnLifecycleEvent(ON_DESTROY)
-        void onDestroy() {
-        }
-    }
-}
diff --git a/lifecycle/runtime/src/test/java/androidx/lifecycle/LifecycleRegistryTest.java b/lifecycle/runtime/src/test/java/androidx/lifecycle/LifecycleRegistryTest.java
new file mode 100644
index 0000000..ed1f7fa
--- /dev/null
+++ b/lifecycle/runtime/src/test/java/androidx/lifecycle/LifecycleRegistryTest.java
@@ -0,0 +1,624 @@
+/*
+ * 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 androidx.lifecycle;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
+
+@RunWith(JUnit4.class)
+public class LifecycleRegistryTest {
+    private LifecycleOwner mLifecycleOwner;
+    private Lifecycle mLifecycle;
+    private LifecycleRegistry mRegistry;
+
+    @Before
+    public void init() {
+        mLifecycleOwner = mock(LifecycleOwner.class);
+        mLifecycle = mock(Lifecycle.class);
+        when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle);
+        mRegistry = new LifecycleRegistry(mLifecycleOwner);
+    }
+
+    @Test
+    public void addRemove() {
+        LifecycleObserver observer = mock(LifecycleObserver.class);
+        mRegistry.addObserver(observer);
+        assertThat(mRegistry.getObserverCount(), is(1));
+        mRegistry.removeObserver(observer);
+        assertThat(mRegistry.getObserverCount(), is(0));
+    }
+
+    @Test
+    public void addGenericAndObserve() {
+        GenericLifecycleObserver generic = mock(GenericLifecycleObserver.class);
+        mRegistry.addObserver(generic);
+        dispatchEvent(ON_CREATE);
+        verify(generic).onStateChanged(mLifecycleOwner, ON_CREATE);
+        reset(generic);
+        dispatchEvent(ON_CREATE);
+        verify(generic, never()).onStateChanged(mLifecycleOwner, ON_CREATE);
+    }
+
+    @Test
+    public void addRegularClass() {
+        TestObserver testObserver = mock(TestObserver.class);
+        mRegistry.addObserver(testObserver);
+        dispatchEvent(ON_START);
+        verify(testObserver, never()).onStop();
+        dispatchEvent(ON_STOP);
+        verify(testObserver).onStop();
+    }
+
+    @Test
+    public void add2RemoveOne() {
+        TestObserver observer1 = mock(TestObserver.class);
+        TestObserver observer2 = mock(TestObserver.class);
+        TestObserver observer3 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        mRegistry.addObserver(observer3);
+
+        dispatchEvent(ON_CREATE);
+
+        verify(observer1).onCreate();
+        verify(observer2).onCreate();
+        verify(observer3).onCreate();
+        reset(observer1, observer2, observer3);
+
+        mRegistry.removeObserver(observer2);
+        dispatchEvent(ON_START);
+
+        verify(observer1).onStart();
+        verify(observer2, never()).onStart();
+        verify(observer3).onStart();
+    }
+
+    @Test
+    public void removeWhileTraversing() {
+        final TestObserver observer2 = mock(TestObserver.class);
+        TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            public void onCreate() {
+                mRegistry.removeObserver(observer2);
+            }
+        });
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        dispatchEvent(ON_CREATE);
+        verify(observer2, never()).onCreate();
+        verify(observer1).onCreate();
+    }
+
+    @Test
+    public void constructionOrder() {
+        fullyInitializeRegistry();
+        final TestObserver observer = mock(TestObserver.class);
+        mRegistry.addObserver(observer);
+        InOrder inOrder = inOrder(observer);
+        inOrder.verify(observer).onCreate();
+        inOrder.verify(observer).onStart();
+        inOrder.verify(observer).onResume();
+    }
+
+    @Test
+    public void constructionDestruction1() {
+        fullyInitializeRegistry();
+        final TestObserver observer = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                dispatchEvent(ON_PAUSE);
+            }
+        });
+        mRegistry.addObserver(observer);
+        InOrder constructionOrder = inOrder(observer);
+        constructionOrder.verify(observer).onCreate();
+        constructionOrder.verify(observer).onStart();
+        constructionOrder.verify(observer, never()).onResume();
+    }
+
+    @Test
+    public void constructionDestruction2() {
+        fullyInitializeRegistry();
+        final TestObserver observer = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                dispatchEvent(ON_PAUSE);
+                dispatchEvent(ON_STOP);
+                dispatchEvent(ON_DESTROY);
+            }
+        });
+        mRegistry.addObserver(observer);
+        InOrder orderVerifier = inOrder(observer);
+        orderVerifier.verify(observer).onCreate();
+        orderVerifier.verify(observer).onStart();
+        orderVerifier.verify(observer).onStop();
+        orderVerifier.verify(observer).onDestroy();
+        orderVerifier.verify(observer, never()).onResume();
+    }
+
+    @Test
+    public void twoObserversChangingState() {
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onCreate() {
+                dispatchEvent(ON_START);
+            }
+        });
+        final TestObserver observer2 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        dispatchEvent(ON_CREATE);
+        verify(observer1, times(1)).onCreate();
+        verify(observer2, times(1)).onCreate();
+        verify(observer1, times(1)).onStart();
+        verify(observer2, times(1)).onStart();
+    }
+
+    @Test
+    public void addDuringTraversing() {
+        final TestObserver observer3 = mock(TestObserver.class);
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            public void onStart() {
+                mRegistry.addObserver(observer3);
+            }
+        });
+        final TestObserver observer2 = mock(TestObserver.class);
+
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+
+        dispatchEvent(ON_CREATE);
+        dispatchEvent(ON_START);
+
+        InOrder inOrder = inOrder(observer1, observer2, observer3);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer2).onStart();
+        inOrder.verify(observer3).onStart();
+    }
+
+    @Test
+    public void addDuringAddition() {
+        final TestObserver observer3 = mock(TestObserver.class);
+        final TestObserver observer2 = spy(new TestObserver() {
+            @Override
+            public void onCreate() {
+                mRegistry.addObserver(observer3);
+            }
+        });
+
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            public void onResume() {
+                mRegistry.addObserver(observer2);
+            }
+        });
+
+        mRegistry.addObserver(observer1);
+
+        dispatchEvent(ON_CREATE);
+        dispatchEvent(ON_START);
+        dispatchEvent(ON_RESUME);
+
+        InOrder inOrder = inOrder(observer1, observer2, observer3);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer1).onResume();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer2).onStart();
+        inOrder.verify(observer2).onResume();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer3).onStart();
+        inOrder.verify(observer3).onResume();
+    }
+
+    @Test
+    public void subscribeToDead() {
+        dispatchEvent(ON_CREATE);
+        final TestObserver observer1 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        verify(observer1).onCreate();
+        dispatchEvent(ON_DESTROY);
+        verify(observer1).onDestroy();
+        final TestObserver observer2 = mock(TestObserver.class);
+        mRegistry.addObserver(observer2);
+        verify(observer2, never()).onCreate();
+        reset(observer1);
+        dispatchEvent(ON_CREATE);
+        verify(observer1).onCreate();
+        verify(observer2).onCreate();
+    }
+
+    @Test
+    public void downEvents() {
+        fullyInitializeRegistry();
+        final TestObserver observer1 = mock(TestObserver.class);
+        final TestObserver observer2 = mock(TestObserver.class);
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        InOrder orderVerifier = inOrder(observer1, observer2);
+        dispatchEvent(ON_PAUSE);
+        orderVerifier.verify(observer2).onPause();
+        orderVerifier.verify(observer1).onPause();
+        dispatchEvent(ON_STOP);
+        orderVerifier.verify(observer2).onStop();
+        orderVerifier.verify(observer1).onStop();
+        dispatchEvent(ON_DESTROY);
+        orderVerifier.verify(observer2).onDestroy();
+        orderVerifier.verify(observer1).onDestroy();
+    }
+
+    @Test
+    public void downEventsAddition() {
+        dispatchEvent(ON_CREATE);
+        dispatchEvent(ON_START);
+        final TestObserver observer1 = mock(TestObserver.class);
+        final TestObserver observer3 = mock(TestObserver.class);
+        final TestObserver observer2 = spy(new TestObserver() {
+            @Override
+            void onStop() {
+                mRegistry.addObserver(observer3);
+            }
+        });
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        InOrder orderVerifier = inOrder(observer1, observer2, observer3);
+        dispatchEvent(ON_STOP);
+        orderVerifier.verify(observer2).onStop();
+        orderVerifier.verify(observer3).onCreate();
+        orderVerifier.verify(observer1).onStop();
+        dispatchEvent(ON_DESTROY);
+        orderVerifier.verify(observer3).onDestroy();
+        orderVerifier.verify(observer2).onDestroy();
+        orderVerifier.verify(observer1).onDestroy();
+    }
+
+    @Test
+    public void downEventsRemoveAll() {
+        fullyInitializeRegistry();
+        final TestObserver observer1 = mock(TestObserver.class);
+        final TestObserver observer3 = mock(TestObserver.class);
+        final TestObserver observer2 = spy(new TestObserver() {
+            @Override
+            void onStop() {
+                mRegistry.removeObserver(observer3);
+                mRegistry.removeObserver(this);
+                mRegistry.removeObserver(observer1);
+                assertThat(mRegistry.getObserverCount(), is(0));
+            }
+        });
+        mRegistry.addObserver(observer1);
+        mRegistry.addObserver(observer2);
+        mRegistry.addObserver(observer3);
+        InOrder orderVerifier = inOrder(observer1, observer2, observer3);
+        dispatchEvent(ON_PAUSE);
+        orderVerifier.verify(observer3).onPause();
+        orderVerifier.verify(observer2).onPause();
+        orderVerifier.verify(observer1).onPause();
+        dispatchEvent(ON_STOP);
+        orderVerifier.verify(observer3).onStop();
+        orderVerifier.verify(observer2).onStop();
+        orderVerifier.verify(observer1, never()).onStop();
+        dispatchEvent(ON_PAUSE);
+        orderVerifier.verify(observer3, never()).onPause();
+        orderVerifier.verify(observer2, never()).onPause();
+        orderVerifier.verify(observer1, never()).onPause();
+    }
+
+    @Test
+    public void deadParentInAddition() {
+        fullyInitializeRegistry();
+        final TestObserver observer2 = mock(TestObserver.class);
+        final TestObserver observer3 = mock(TestObserver.class);
+
+        TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                mRegistry.removeObserver(this);
+                assertThat(mRegistry.getObserverCount(), is(0));
+                mRegistry.addObserver(observer2);
+                mRegistry.addObserver(observer3);
+            }
+        });
+
+        mRegistry.addObserver(observer1);
+
+        InOrder inOrder = inOrder(observer1, observer2, observer3);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer2).onStart();
+        inOrder.verify(observer2).onResume();
+        inOrder.verify(observer3).onStart();
+        inOrder.verify(observer3).onResume();
+    }
+
+    @Test
+    public void deadParentWhileTraversing() {
+        final TestObserver observer2 = mock(TestObserver.class);
+        final TestObserver observer3 = mock(TestObserver.class);
+        TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                mRegistry.removeObserver(this);
+                assertThat(mRegistry.getObserverCount(), is(0));
+                mRegistry.addObserver(observer2);
+                mRegistry.addObserver(observer3);
+            }
+        });
+        InOrder inOrder = inOrder(observer1, observer2, observer3);
+        mRegistry.addObserver(observer1);
+        dispatchEvent(ON_CREATE);
+        dispatchEvent(ON_START);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer2).onStart();
+        inOrder.verify(observer3).onStart();
+    }
+
+    @Test
+    public void removeCascade() {
+        final TestObserver observer3 = mock(TestObserver.class);
+        final TestObserver observer4 = mock(TestObserver.class);
+
+        final TestObserver observer2 = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                mRegistry.removeObserver(this);
+            }
+        });
+
+        TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onResume() {
+                mRegistry.removeObserver(this);
+                mRegistry.addObserver(observer2);
+                mRegistry.addObserver(observer3);
+                mRegistry.addObserver(observer4);
+            }
+        });
+        fullyInitializeRegistry();
+        mRegistry.addObserver(observer1);
+        InOrder inOrder = inOrder(observer1, observer2, observer3, observer4);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer1).onResume();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer2).onStart();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer3).onStart();
+        inOrder.verify(observer4).onCreate();
+        inOrder.verify(observer4).onStart();
+        inOrder.verify(observer3).onResume();
+        inOrder.verify(observer4).onResume();
+    }
+
+    @Test
+    public void changeStateDuringDescending() {
+        final TestObserver observer2 = mock(TestObserver.class);
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onPause() {
+                // but tonight I bounce back
+                mRegistry.handleLifecycleEvent(ON_RESUME);
+                mRegistry.addObserver(observer2);
+            }
+        });
+        fullyInitializeRegistry();
+        mRegistry.addObserver(observer1);
+        mRegistry.handleLifecycleEvent(ON_PAUSE);
+        InOrder inOrder = inOrder(observer1, observer2);
+        inOrder.verify(observer1).onPause();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer2).onStart();
+        inOrder.verify(observer1).onResume();
+        inOrder.verify(observer2).onResume();
+    }
+
+    @Test
+    public void siblingLimitationCheck() {
+        fullyInitializeRegistry();
+        final TestObserver observer2 = mock(TestObserver.class);
+        final TestObserver observer3 = mock(TestObserver.class);
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                mRegistry.addObserver(observer2);
+            }
+
+            @Override
+            void onResume() {
+                mRegistry.addObserver(observer3);
+            }
+        });
+        mRegistry.addObserver(observer1);
+        InOrder inOrder = inOrder(observer1, observer2, observer3);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer1).onResume();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer2).onStart();
+        inOrder.verify(observer2).onResume();
+        inOrder.verify(observer3).onStart();
+        inOrder.verify(observer3).onResume();
+    }
+
+    @Test
+    public void siblingRemovalLimitationCheck1() {
+        fullyInitializeRegistry();
+        final TestObserver observer2 = mock(TestObserver.class);
+        final TestObserver observer3 = mock(TestObserver.class);
+        final TestObserver observer4 = mock(TestObserver.class);
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                mRegistry.addObserver(observer2);
+            }
+
+            @Override
+            void onResume() {
+                mRegistry.removeObserver(observer2);
+                mRegistry.addObserver(observer3);
+                mRegistry.addObserver(observer4);
+            }
+        });
+        mRegistry.addObserver(observer1);
+        InOrder inOrder = inOrder(observer1, observer2, observer3, observer4);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer1).onResume();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer3).onStart();
+        inOrder.verify(observer4).onCreate();
+        inOrder.verify(observer4).onStart();
+        inOrder.verify(observer3).onResume();
+        inOrder.verify(observer4).onResume();
+    }
+
+    @Test
+    public void siblingRemovalLimitationCheck2() {
+        fullyInitializeRegistry();
+        final TestObserver observer2 = mock(TestObserver.class);
+        final TestObserver observer3 = spy(new TestObserver() {
+            @Override
+            void onCreate() {
+                mRegistry.removeObserver(observer2);
+            }
+        });
+        final TestObserver observer4 = mock(TestObserver.class);
+        final TestObserver observer1 = spy(new TestObserver() {
+            @Override
+            void onStart() {
+                mRegistry.addObserver(observer2);
+            }
+
+            @Override
+            void onResume() {
+                mRegistry.addObserver(observer3);
+                mRegistry.addObserver(observer4);
+            }
+        });
+
+        mRegistry.addObserver(observer1);
+        InOrder inOrder = inOrder(observer1, observer2, observer3, observer4);
+        inOrder.verify(observer1).onCreate();
+        inOrder.verify(observer1).onStart();
+        inOrder.verify(observer2).onCreate();
+        inOrder.verify(observer1).onResume();
+        inOrder.verify(observer3).onCreate();
+        inOrder.verify(observer3).onStart();
+        inOrder.verify(observer4).onCreate();
+        inOrder.verify(observer4).onStart();
+        inOrder.verify(observer3).onResume();
+        inOrder.verify(observer4).onResume();
+    }
+
+    @Test
+    public void sameObserverReAddition() {
+        TestObserver observer = mock(TestObserver.class);
+        mRegistry.addObserver(observer);
+        mRegistry.removeObserver(observer);
+        mRegistry.addObserver(observer);
+        dispatchEvent(ON_CREATE);
+        verify(observer).onCreate();
+    }
+
+    private static void forceGc() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+    }
+
+    @Test
+    public void goneLifecycleOwner() {
+        fullyInitializeRegistry();
+        mLifecycleOwner = null;
+        forceGc();
+        TestObserver observer = mock(TestObserver.class);
+        mRegistry.addObserver(observer);
+        verify(observer, never()).onCreate();
+        verify(observer, never()).onStart();
+        verify(observer, never()).onResume();
+    }
+
+    private void dispatchEvent(Lifecycle.Event event) {
+        when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
+        mRegistry.handleLifecycleEvent(event);
+    }
+
+    private void fullyInitializeRegistry() {
+        dispatchEvent(ON_CREATE);
+        dispatchEvent(ON_START);
+        dispatchEvent(ON_RESUME);
+    }
+
+    private abstract class TestObserver implements LifecycleObserver {
+        @OnLifecycleEvent(ON_CREATE)
+        void onCreate() {
+        }
+
+        @OnLifecycleEvent(ON_START)
+        void onStart() {
+        }
+
+        @OnLifecycleEvent(ON_RESUME)
+        void onResume() {
+        }
+
+        @OnLifecycleEvent(ON_PAUSE)
+        void onPause() {
+        }
+
+        @OnLifecycleEvent(ON_STOP)
+        void onStop() {
+        }
+
+        @OnLifecycleEvent(ON_DESTROY)
+        void onDestroy() {
+        }
+    }
+}
diff --git a/lifecycle/viewmodel/api/current.txt b/lifecycle/viewmodel/api/current.txt
index 6a82d4c..802ac19 100644
--- a/lifecycle/viewmodel/api/current.txt
+++ b/lifecycle/viewmodel/api/current.txt
@@ -1,6 +1,6 @@
-package android.arch.lifecycle {
+package androidx.lifecycle {
 
-  public class AndroidViewModel extends android.arch.lifecycle.ViewModel {
+  public class AndroidViewModel extends androidx.lifecycle.ViewModel {
     ctor public AndroidViewModel(android.app.Application);
     method public <T extends android.app.Application> T getApplication();
   }
@@ -11,24 +11,24 @@
   }
 
   public class ViewModelProvider {
-    ctor public ViewModelProvider(android.arch.lifecycle.ViewModelStoreOwner, android.arch.lifecycle.ViewModelProvider.Factory);
-    ctor public ViewModelProvider(android.arch.lifecycle.ViewModelStore, android.arch.lifecycle.ViewModelProvider.Factory);
-    method public <T extends android.arch.lifecycle.ViewModel> T get(java.lang.Class<T>);
-    method public <T extends android.arch.lifecycle.ViewModel> T get(java.lang.String, java.lang.Class<T>);
+    ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner, androidx.lifecycle.ViewModelProvider.Factory);
+    ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore, androidx.lifecycle.ViewModelProvider.Factory);
+    method public <T extends androidx.lifecycle.ViewModel> T get(java.lang.Class<T>);
+    method public <T extends androidx.lifecycle.ViewModel> T get(java.lang.String, java.lang.Class<T>);
   }
 
-  public static class ViewModelProvider.AndroidViewModelFactory extends android.arch.lifecycle.ViewModelProvider.NewInstanceFactory {
+  public static class ViewModelProvider.AndroidViewModelFactory extends androidx.lifecycle.ViewModelProvider.NewInstanceFactory {
     ctor public ViewModelProvider.AndroidViewModelFactory(android.app.Application);
-    method public static android.arch.lifecycle.ViewModelProvider.AndroidViewModelFactory getInstance(android.app.Application);
+    method public static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory getInstance(android.app.Application);
   }
 
   public static abstract interface ViewModelProvider.Factory {
-    method public abstract <T extends android.arch.lifecycle.ViewModel> T create(java.lang.Class<T>);
+    method public abstract <T extends androidx.lifecycle.ViewModel> T create(java.lang.Class<T>);
   }
 
-  public static class ViewModelProvider.NewInstanceFactory implements android.arch.lifecycle.ViewModelProvider.Factory {
+  public static class ViewModelProvider.NewInstanceFactory implements androidx.lifecycle.ViewModelProvider.Factory {
     ctor public ViewModelProvider.NewInstanceFactory();
-    method public <T extends android.arch.lifecycle.ViewModel> T create(java.lang.Class<T>);
+    method public <T extends androidx.lifecycle.ViewModel> T create(java.lang.Class<T>);
   }
 
   public class ViewModelStore {
@@ -37,7 +37,7 @@
   }
 
   public abstract interface ViewModelStoreOwner {
-    method public abstract android.arch.lifecycle.ViewModelStore getViewModelStore();
+    method public abstract androidx.lifecycle.ViewModelStore getViewModelStore();
   }
 
 }
diff --git a/lifecycle/viewmodel/api/1.1.0.txt b/lifecycle/viewmodel/api_legacy/1.1.0.txt
similarity index 100%
rename from lifecycle/viewmodel/api/1.1.0.txt
rename to lifecycle/viewmodel/api_legacy/1.1.0.txt
diff --git a/lifecycle/viewmodel/api/1.1.0.txt b/lifecycle/viewmodel/api_legacy/current.txt
similarity index 100%
copy from lifecycle/viewmodel/api/1.1.0.txt
copy to lifecycle/viewmodel/api_legacy/current.txt
diff --git a/lifecycle/viewmodel/proguard-rules.pro b/lifecycle/viewmodel/proguard-rules.pro
index daac94e..7f40702 100644
--- a/lifecycle/viewmodel/proguard-rules.pro
+++ b/lifecycle/viewmodel/proguard-rules.pro
@@ -1,3 +1,3 @@
--keep class * extends android.arch.lifecycle.ViewModel {
+-keep class * extends androidx.lifecycle.ViewModel {
     <init>();
 }
\ No newline at end of file
diff --git a/lifecycle/viewmodel/src/main/AndroidManifest.xml b/lifecycle/viewmodel/src/main/AndroidManifest.xml
index c5d9c0d..0fbbcfb 100644
--- a/lifecycle/viewmodel/src/main/AndroidManifest.xml
+++ b/lifecycle/viewmodel/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.lifecycle.viewmodel">
+          package="androidx.lifecycle.viewmodel">
 </manifest>
diff --git a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/AndroidViewModel.java b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/AndroidViewModel.java
deleted file mode 100644
index e8895bd..0000000
--- a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/AndroidViewModel.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.annotation.SuppressLint;
-import android.app.Application;
-import android.support.annotation.NonNull;
-
-/**
- * Application context aware {@link ViewModel}.
- * <p>
- * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
- * <p>
- */
-public class AndroidViewModel extends ViewModel {
-    @SuppressLint("StaticFieldLeak")
-    private Application mApplication;
-
-    public AndroidViewModel(@NonNull Application application) {
-        mApplication = application;
-    }
-
-    /**
-     * Return the application.
-     */
-    @SuppressWarnings("TypeParameterUnusedInFormals")
-    @NonNull
-    public <T extends Application> T getApplication() {
-        //noinspection unchecked
-        return (T) mApplication;
-    }
-}
diff --git a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModel.java b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModel.java
deleted file mode 100644
index 0310c46..0000000
--- a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModel.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-/**
- * ViewModel is a class that is responsible for preparing and managing the data for
- * an {@link android.app.Activity Activity} or a {@link android.support.v4.app.Fragment Fragment}.
- * It also handles the communication of the Activity / Fragment with the rest of the application
- * (e.g. calling the business logic classes).
- * <p>
- * A ViewModel is always created in association with a scope (an fragment or an activity) and will
- * be retained as long as the scope is alive. E.g. if it is an Activity, until it is
- * finished.
- * <p>
- * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
- * configuration change (e.g. rotation). The new instance of the owner will just re-connected to the
- * existing ViewModel.
- * <p>
- * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
- * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
- * ViewModel. ViewModels usually expose this information via {@link LiveData} or Android Data
- * Binding. You can also use any observability construct from you favorite framework.
- * <p>
- * ViewModel's only responsibility is to manage the data for the UI. It <b>should never</b> access
- * your view hierarchy or hold a reference back to the Activity or the Fragment.
- * <p>
- * Typical usage from an Activity standpoint would be:
- * <pre>
- * public class UserActivity extends Activity {
- *
- *     {@literal @}Override
- *     protected void onCreate(Bundle savedInstanceState) {
- *         super.onCreate(savedInstanceState);
- *         setContentView(R.layout.user_activity_layout);
- *         final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
- *         viewModel.userLiveData.observer(this, new Observer<User>() {
- *            {@literal @}Override
- *             public void onChanged(@Nullable User data) {
- *                 // update ui.
- *             }
- *         });
- *         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
- *             {@literal @}Override
- *             public void onClick(View v) {
- *                  viewModel.doAction();
- *             }
- *         });
- *     }
- * }
- * </pre>
- *
- * ViewModel would be:
- * <pre>
- * public class UserModel extends ViewModel {
- *     public final LiveData&lt;User&gt; userLiveData = new LiveData<>();
- *
- *     public UserModel() {
- *         // trigger user load.
- *     }
- *
- *     void doAction() {
- *         // depending on the action, do necessary business logic calls and update the
- *         // userLiveData.
- *     }
- * }
- * </pre>
- *
- * <p>
- * ViewModels can also be used as a communication layer between different Fragments of an Activity.
- * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
- * communication between Fragments in a de-coupled fashion such that they never need to talk to
- * the other Fragment directly.
- * <pre>
- * public class MyFragment extends Fragment {
- *     public void onStart() {
- *         UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
- *     }
- * }
- * </pre>
- * </>
- */
-public abstract class ViewModel {
-    /**
-     * This method will be called when this ViewModel is no longer used and will be destroyed.
-     * <p>
-     * It is useful when ViewModel observes some data and you need to clear this subscription to
-     * prevent a leak of this ViewModel.
-     */
-    @SuppressWarnings("WeakerAccess")
-    protected void onCleared() {
-    }
-}
diff --git a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelProvider.java b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelProvider.java
deleted file mode 100644
index 4f25ac9..0000000
--- a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelProvider.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.app.Application;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * An utility class that provides {@code ViewModels} for a scope.
- * <p>
- * Default {@code ViewModelProvider} for an {@code Activity} or a {@code Fragment} can be obtained
- * from {@link android.arch.lifecycle.ViewModelProviders} class.
- */
-@SuppressWarnings("WeakerAccess")
-public class ViewModelProvider {
-
-    private static final String DEFAULT_KEY =
-            "android.arch.lifecycle.ViewModelProvider.DefaultKey";
-
-    /**
-     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
-     */
-    public interface Factory {
-        /**
-         * Creates a new instance of the given {@code Class}.
-         * <p>
-         *
-         * @param modelClass a {@code Class} whose instance is requested
-         * @param <T>        The type parameter for the ViewModel.
-         * @return a newly created ViewModel
-         */
-        @NonNull
-        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
-    }
-
-    private final Factory mFactory;
-    private final ViewModelStore mViewModelStore;
-
-    /**
-     * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
-     * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
-     *
-     * @param owner   a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
-     *                retain {@code ViewModels}
-     * @param factory a {@code Factory} which will be used to instantiate
-     *                new {@code ViewModels}
-     */
-    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
-        this(owner.getViewModelStore(), factory);
-    }
-
-    /**
-     * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
-     * {@code Factory} and retain them in the given {@code store}.
-     *
-     * @param store   {@code ViewModelStore} where ViewModels will be stored.
-     * @param factory factory a {@code Factory} which will be used to instantiate
-     *                new {@code ViewModels}
-     */
-    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
-        mFactory = factory;
-        this.mViewModelStore = store;
-    }
-
-    /**
-     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
-     * an activity), associated with this {@code ViewModelProvider}.
-     * <p>
-     * The created ViewModel is associated with the given scope and will be retained
-     * as long as the scope is alive (e.g. if it is an activity, until it is
-     * finished or process is killed).
-     *
-     * @param modelClass The class of the ViewModel to create an instance of it if it is not
-     *                   present.
-     * @param <T>        The type parameter for the ViewModel.
-     * @return A ViewModel that is an instance of the given type {@code T}.
-     */
-    @NonNull
-    @MainThread
-    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
-        String canonicalName = modelClass.getCanonicalName();
-        if (canonicalName == null) {
-            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
-        }
-        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
-    }
-
-    /**
-     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
-     * an activity), associated with this {@code ViewModelProvider}.
-     * <p>
-     * The created ViewModel is associated with the given scope and will be retained
-     * as long as the scope is alive (e.g. if it is an activity, until it is
-     * finished or process is killed).
-     *
-     * @param key        The key to use to identify the ViewModel.
-     * @param modelClass The class of the ViewModel to create an instance of it if it is not
-     *                   present.
-     * @param <T>        The type parameter for the ViewModel.
-     * @return A ViewModel that is an instance of the given type {@code T}.
-     */
-    @NonNull
-    @MainThread
-    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
-        ViewModel viewModel = mViewModelStore.get(key);
-
-        if (modelClass.isInstance(viewModel)) {
-            //noinspection unchecked
-            return (T) viewModel;
-        } else {
-            //noinspection StatementWithEmptyBody
-            if (viewModel != null) {
-                // TODO: log a warning.
-            }
-        }
-
-        viewModel = mFactory.create(modelClass);
-        mViewModelStore.put(key, viewModel);
-        //noinspection unchecked
-        return (T) viewModel;
-    }
-
-    /**
-     * Simple factory, which calls empty constructor on the give class.
-     */
-    public static class NewInstanceFactory implements Factory {
-
-        @SuppressWarnings("ClassNewInstance")
-        @NonNull
-        @Override
-        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
-            //noinspection TryWithIdenticalCatches
-            try {
-                return modelClass.newInstance();
-            } catch (InstantiationException e) {
-                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
-            } catch (IllegalAccessException e) {
-                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
-            }
-        }
-    }
-
-    /**
-     * {@link Factory} which may create {@link AndroidViewModel} and
-     * {@link ViewModel}, which have an empty constructor.
-     */
-    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
-
-        private static AndroidViewModelFactory sInstance;
-
-        /**
-         * Retrieve a singleton instance of AndroidViewModelFactory.
-         *
-         * @param application an application to pass in {@link AndroidViewModel}
-         * @return A valid {@link AndroidViewModelFactory}
-         */
-        @NonNull
-        public static AndroidViewModelFactory getInstance(@NonNull Application application) {
-            if (sInstance == null) {
-                sInstance = new AndroidViewModelFactory(application);
-            }
-            return sInstance;
-        }
-
-        private Application mApplication;
-
-        /**
-         * Creates a {@code AndroidViewModelFactory}
-         *
-         * @param application an application to pass in {@link AndroidViewModel}
-         */
-        public AndroidViewModelFactory(@NonNull Application application) {
-            mApplication = application;
-        }
-
-        @NonNull
-        @Override
-        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
-            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
-                //noinspection TryWithIdenticalCatches
-                try {
-                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
-                } catch (NoSuchMethodException e) {
-                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
-                } catch (IllegalAccessException e) {
-                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
-                } catch (InstantiationException e) {
-                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
-                } catch (InvocationTargetException e) {
-                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
-                }
-            }
-            return super.create(modelClass);
-        }
-    }
-}
diff --git a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelStore.java b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelStore.java
deleted file mode 100644
index 74b5e24..0000000
--- a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelStore.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import java.util.HashMap;
-
-/**
- * Class to store {@code ViewModels}.
- * <p>
- * An instance of {@code ViewModelStore} must be retained through configuration changes:
- * if an owner of this {@code ViewModelStore} is destroyed and recreated due to configuration
- * changes, new instance of an owner should still have the same old instance of
- * {@code ViewModelStore}.
- * <p>
- * If an owner of this {@code ViewModelStore} is destroyed and is not going to be recreated,
- * then it should call {@link #clear()} on this {@code ViewModelStore}, so {@code ViewModels} would
- * be notified that they are no longer used.
- * <p>
- * {@link android.arch.lifecycle.ViewModelStores} provides a {@code ViewModelStore} for
- * activities and fragments.
- */
-public class ViewModelStore {
-
-    private final HashMap<String, ViewModel> mMap = new HashMap<>();
-
-    final void put(String key, ViewModel viewModel) {
-        ViewModel oldViewModel = mMap.put(key, viewModel);
-        if (oldViewModel != null) {
-            oldViewModel.onCleared();
-        }
-    }
-
-    final ViewModel get(String key) {
-        return mMap.get(key);
-    }
-
-    /**
-     *  Clears internal storage and notifies ViewModels that they are no longer used.
-     */
-    public final void clear() {
-        for (ViewModel vm : mMap.values()) {
-            vm.onCleared();
-        }
-        mMap.clear();
-    }
-}
diff --git a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelStoreOwner.java b/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelStoreOwner.java
deleted file mode 100644
index e26fa32..0000000
--- a/lifecycle/viewmodel/src/main/java/android/arch/lifecycle/ViewModelStoreOwner.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import android.support.annotation.NonNull;
-
-/**
- * A scope that owns {@link ViewModelStore}.
- * <p>
- * A responsibility of an implementation of this interface is to retain owned ViewModelStore
- * during the configuration changes and call {@link ViewModelStore#clear()}, when this scope is
- * going to be destroyed.
- */
-@SuppressWarnings("WeakerAccess")
-public interface ViewModelStoreOwner {
-    /**
-     * Returns owned {@link ViewModelStore}
-     *
-     * @return a {@code ViewModelStore}
-     */
-    @NonNull
-    ViewModelStore getViewModelStore();
-}
diff --git a/lifecycle/viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.java b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.java
new file mode 100644
index 0000000..1782453
--- /dev/null
+++ b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.java
@@ -0,0 +1,47 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Application context aware {@link ViewModel}.
+ * <p>
+ * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
+ * <p>
+ */
+public class AndroidViewModel extends ViewModel {
+    @SuppressLint("StaticFieldLeak")
+    private Application mApplication;
+
+    public AndroidViewModel(@NonNull Application application) {
+        mApplication = application;
+    }
+
+    /**
+     * Return the application.
+     */
+    @SuppressWarnings("TypeParameterUnusedInFormals")
+    @NonNull
+    public <T extends Application> T getApplication() {
+        //noinspection unchecked
+        return (T) mApplication;
+    }
+}
diff --git a/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModel.java b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModel.java
new file mode 100644
index 0000000..ef9ce7f
--- /dev/null
+++ b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModel.java
@@ -0,0 +1,106 @@
+/*
+ * 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 androidx.lifecycle;
+
+/**
+ * ViewModel is a class that is responsible for preparing and managing the data for
+ * an {@link android.app.Activity Activity} or a {@link androidx.fragment.app.Fragment Fragment}.
+ * It also handles the communication of the Activity / Fragment with the rest of the application
+ * (e.g. calling the business logic classes).
+ * <p>
+ * A ViewModel is always created in association with a scope (an fragment or an activity) and will
+ * be retained as long as the scope is alive. E.g. if it is an Activity, until it is
+ * finished.
+ * <p>
+ * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
+ * configuration change (e.g. rotation). The new instance of the owner will just re-connected to the
+ * existing ViewModel.
+ * <p>
+ * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
+ * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
+ * ViewModel. ViewModels usually expose this information via {@link LiveData} or Android Data
+ * Binding. You can also use any observability construct from you favorite framework.
+ * <p>
+ * ViewModel's only responsibility is to manage the data for the UI. It <b>should never</b> access
+ * your view hierarchy or hold a reference back to the Activity or the Fragment.
+ * <p>
+ * Typical usage from an Activity standpoint would be:
+ * <pre>
+ * public class UserActivity extends Activity {
+ *
+ *     {@literal @}Override
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *         setContentView(R.layout.user_activity_layout);
+ *         final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
+ *         viewModel.userLiveData.observer(this, new Observer<User>() {
+ *            {@literal @}Override
+ *             public void onChanged(@Nullable User data) {
+ *                 // update ui.
+ *             }
+ *         });
+ *         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
+ *             {@literal @}Override
+ *             public void onClick(View v) {
+ *                  viewModel.doAction();
+ *             }
+ *         });
+ *     }
+ * }
+ * </pre>
+ *
+ * ViewModel would be:
+ * <pre>
+ * public class UserModel extends ViewModel {
+ *     public final LiveData&lt;User&gt; userLiveData = new LiveData<>();
+ *
+ *     public UserModel() {
+ *         // trigger user load.
+ *     }
+ *
+ *     void doAction() {
+ *         // depending on the action, do necessary business logic calls and update the
+ *         // userLiveData.
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>
+ * ViewModels can also be used as a communication layer between different Fragments of an Activity.
+ * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
+ * communication between Fragments in a de-coupled fashion such that they never need to talk to
+ * the other Fragment directly.
+ * <pre>
+ * public class MyFragment extends Fragment {
+ *     public void onStart() {
+ *         UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
+ *     }
+ * }
+ * </pre>
+ * </>
+ */
+public abstract class ViewModel {
+    /**
+     * This method will be called when this ViewModel is no longer used and will be destroyed.
+     * <p>
+     * It is useful when ViewModel observes some data and you need to clear this subscription to
+     * prevent a leak of this ViewModel.
+     */
+    @SuppressWarnings("WeakerAccess")
+    protected void onCleared() {
+    }
+}
diff --git a/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java
new file mode 100644
index 0000000..8e3ba9b
--- /dev/null
+++ b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java
@@ -0,0 +1,214 @@
+/*
+ * 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 androidx.lifecycle;
+
+import android.app.Application;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * An utility class that provides {@code ViewModels} for a scope.
+ * <p>
+ * Default {@code ViewModelProvider} for an {@code Activity} or a {@code Fragment} can be obtained
+ * from {@link androidx.lifecycle.ViewModelProviders} class.
+ */
+@SuppressWarnings("WeakerAccess")
+public class ViewModelProvider {
+
+    private static final String DEFAULT_KEY =
+            "androidx.lifecycle.ViewModelProvider.DefaultKey";
+
+    /**
+     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
+     */
+    public interface Factory {
+        /**
+         * Creates a new instance of the given {@code Class}.
+         * <p>
+         *
+         * @param modelClass a {@code Class} whose instance is requested
+         * @param <T>        The type parameter for the ViewModel.
+         * @return a newly created ViewModel
+         */
+        @NonNull
+        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
+    }
+
+    private final Factory mFactory;
+    private final ViewModelStore mViewModelStore;
+
+    /**
+     * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
+     * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
+     *
+     * @param owner   a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
+     *                retain {@code ViewModels}
+     * @param factory a {@code Factory} which will be used to instantiate
+     *                new {@code ViewModels}
+     */
+    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
+        this(owner.getViewModelStore(), factory);
+    }
+
+    /**
+     * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
+     * {@code Factory} and retain them in the given {@code store}.
+     *
+     * @param store   {@code ViewModelStore} where ViewModels will be stored.
+     * @param factory factory a {@code Factory} which will be used to instantiate
+     *                new {@code ViewModels}
+     */
+    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
+        mFactory = factory;
+        this.mViewModelStore = store;
+    }
+
+    /**
+     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+     * an activity), associated with this {@code ViewModelProvider}.
+     * <p>
+     * The created ViewModel is associated with the given scope and will be retained
+     * as long as the scope is alive (e.g. if it is an activity, until it is
+     * finished or process is killed).
+     *
+     * @param modelClass The class of the ViewModel to create an instance of it if it is not
+     *                   present.
+     * @param <T>        The type parameter for the ViewModel.
+     * @return A ViewModel that is an instance of the given type {@code T}.
+     */
+    @NonNull
+    @MainThread
+    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
+        String canonicalName = modelClass.getCanonicalName();
+        if (canonicalName == null) {
+            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
+        }
+        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
+    }
+
+    /**
+     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
+     * an activity), associated with this {@code ViewModelProvider}.
+     * <p>
+     * The created ViewModel is associated with the given scope and will be retained
+     * as long as the scope is alive (e.g. if it is an activity, until it is
+     * finished or process is killed).
+     *
+     * @param key        The key to use to identify the ViewModel.
+     * @param modelClass The class of the ViewModel to create an instance of it if it is not
+     *                   present.
+     * @param <T>        The type parameter for the ViewModel.
+     * @return A ViewModel that is an instance of the given type {@code T}.
+     */
+    @NonNull
+    @MainThread
+    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
+        ViewModel viewModel = mViewModelStore.get(key);
+
+        if (modelClass.isInstance(viewModel)) {
+            //noinspection unchecked
+            return (T) viewModel;
+        } else {
+            //noinspection StatementWithEmptyBody
+            if (viewModel != null) {
+                // TODO: log a warning.
+            }
+        }
+
+        viewModel = mFactory.create(modelClass);
+        mViewModelStore.put(key, viewModel);
+        //noinspection unchecked
+        return (T) viewModel;
+    }
+
+    /**
+     * Simple factory, which calls empty constructor on the give class.
+     */
+    public static class NewInstanceFactory implements Factory {
+
+        @SuppressWarnings("ClassNewInstance")
+        @NonNull
+        @Override
+        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+            //noinspection TryWithIdenticalCatches
+            try {
+                return modelClass.newInstance();
+            } catch (InstantiationException e) {
+                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+            }
+        }
+    }
+
+    /**
+     * {@link Factory} which may create {@link AndroidViewModel} and
+     * {@link ViewModel}, which have an empty constructor.
+     */
+    public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
+
+        private static AndroidViewModelFactory sInstance;
+
+        /**
+         * Retrieve a singleton instance of AndroidViewModelFactory.
+         *
+         * @param application an application to pass in {@link AndroidViewModel}
+         * @return A valid {@link AndroidViewModelFactory}
+         */
+        @NonNull
+        public static AndroidViewModelFactory getInstance(@NonNull Application application) {
+            if (sInstance == null) {
+                sInstance = new AndroidViewModelFactory(application);
+            }
+            return sInstance;
+        }
+
+        private Application mApplication;
+
+        /**
+         * Creates a {@code AndroidViewModelFactory}
+         *
+         * @param application an application to pass in {@link AndroidViewModel}
+         */
+        public AndroidViewModelFactory(@NonNull Application application) {
+            mApplication = application;
+        }
+
+        @NonNull
+        @Override
+        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
+                //noinspection TryWithIdenticalCatches
+                try {
+                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
+                } catch (NoSuchMethodException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                } catch (InstantiationException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
+                }
+            }
+            return super.create(modelClass);
+        }
+    }
+}
diff --git a/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelStore.java b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelStore.java
new file mode 100644
index 0000000..c831563
--- /dev/null
+++ b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelStore.java
@@ -0,0 +1,60 @@
+/*
+ * 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 androidx.lifecycle;
+
+import java.util.HashMap;
+
+/**
+ * Class to store {@code ViewModels}.
+ * <p>
+ * An instance of {@code ViewModelStore} must be retained through configuration changes:
+ * if an owner of this {@code ViewModelStore} is destroyed and recreated due to configuration
+ * changes, new instance of an owner should still have the same old instance of
+ * {@code ViewModelStore}.
+ * <p>
+ * If an owner of this {@code ViewModelStore} is destroyed and is not going to be recreated,
+ * then it should call {@link #clear()} on this {@code ViewModelStore}, so {@code ViewModels} would
+ * be notified that they are no longer used.
+ * <p>
+ * {@link androidx.lifecycle.ViewModelStores} provides a {@code ViewModelStore} for
+ * activities and fragments.
+ */
+public class ViewModelStore {
+
+    private final HashMap<String, ViewModel> mMap = new HashMap<>();
+
+    final void put(String key, ViewModel viewModel) {
+        ViewModel oldViewModel = mMap.put(key, viewModel);
+        if (oldViewModel != null) {
+            oldViewModel.onCleared();
+        }
+    }
+
+    final ViewModel get(String key) {
+        return mMap.get(key);
+    }
+
+    /**
+     *  Clears internal storage and notifies ViewModels that they are no longer used.
+     */
+    public final void clear() {
+        for (ViewModel vm : mMap.values()) {
+            vm.onCleared();
+        }
+        mMap.clear();
+    }
+}
diff --git a/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelStoreOwner.java b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelStoreOwner.java
new file mode 100644
index 0000000..d9bec9f
--- /dev/null
+++ b/lifecycle/viewmodel/src/main/java/androidx/lifecycle/ViewModelStoreOwner.java
@@ -0,0 +1,37 @@
+/*
+ * 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 androidx.lifecycle;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A scope that owns {@link ViewModelStore}.
+ * <p>
+ * A responsibility of an implementation of this interface is to retain owned ViewModelStore
+ * during the configuration changes and call {@link ViewModelStore#clear()}, when this scope is
+ * going to be destroyed.
+ */
+@SuppressWarnings("WeakerAccess")
+public interface ViewModelStoreOwner {
+    /**
+     * Returns owned {@link ViewModelStore}
+     *
+     * @return a {@code ViewModelStore}
+     */
+    @NonNull
+    ViewModelStore getViewModelStore();
+}
diff --git a/lifecycle/viewmodel/src/test/java/android/arch/lifecycle/ViewModelProviderTest.java b/lifecycle/viewmodel/src/test/java/android/arch/lifecycle/ViewModelProviderTest.java
deleted file mode 100644
index 142f19a..0000000
--- a/lifecycle/viewmodel/src/test/java/android/arch/lifecycle/ViewModelProviderTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.lifecycle.ViewModelProvider.NewInstanceFactory;
-import android.support.annotation.NonNull;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class ViewModelProviderTest {
-
-    private ViewModelProvider mViewModelProvider;
-
-    @Before
-    public void setup() {
-        mViewModelProvider = new ViewModelProvider(new ViewModelStore(), new NewInstanceFactory());
-    }
-
-    @Test
-    public void twoViewModelsWithSameKey() throws Throwable {
-        String key = "the_key";
-        ViewModel1 vm1 = mViewModelProvider.get(key, ViewModel1.class);
-        assertThat(vm1.mCleared, is(false));
-        ViewModel2 vw2 = mViewModelProvider.get(key, ViewModel2.class);
-        assertThat(vw2, notNullValue());
-        assertThat(vm1.mCleared, is(true));
-    }
-
-
-    @Test
-    public void localViewModel() throws Throwable {
-        class VM extends ViewModel1 {
-        }
-        try {
-            mViewModelProvider.get(VM.class);
-            Assert.fail();
-        } catch (IllegalArgumentException ignored) {
-        }
-    }
-
-    @Test
-    public void twoViewModels() {
-        ViewModel1 model1 = mViewModelProvider.get(ViewModel1.class);
-        ViewModel2 model2 = mViewModelProvider.get(ViewModel2.class);
-        assertThat(mViewModelProvider.get(ViewModel1.class), is(model1));
-        assertThat(mViewModelProvider.get(ViewModel2.class), is(model2));
-    }
-
-    @Test
-    public void testOwnedBy() {
-        final ViewModelStore store = new ViewModelStore();
-        ViewModelStoreOwner owner = new ViewModelStoreOwner() {
-            @NonNull
-            @Override
-            public ViewModelStore getViewModelStore() {
-                return store;
-            }
-        };
-        ViewModelProvider provider = new ViewModelProvider(owner, new NewInstanceFactory());
-        ViewModel1 viewModel = provider.get(ViewModel1.class);
-        assertThat(viewModel, is(provider.get(ViewModel1.class)));
-    }
-
-    public static class ViewModel1 extends ViewModel {
-        boolean mCleared;
-
-        @Override
-        protected void onCleared() {
-            mCleared = true;
-        }
-    }
-
-    public static class ViewModel2 extends ViewModel {
-    }
-}
diff --git a/lifecycle/viewmodel/src/test/java/android/arch/lifecycle/ViewModelStoreTest.java b/lifecycle/viewmodel/src/test/java/android/arch/lifecycle/ViewModelStoreTest.java
deleted file mode 100644
index cfeefb7..0000000
--- a/lifecycle/viewmodel/src/test/java/android/arch/lifecycle/ViewModelStoreTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class ViewModelStoreTest {
-
-    @Test
-    public void testClear() {
-        ViewModelStore store = new ViewModelStore();
-        TestViewModel viewModel1 = new TestViewModel();
-        TestViewModel viewModel2 = new TestViewModel();
-        store.put("a", viewModel1);
-        store.put("b", viewModel2);
-        assertThat(viewModel1.mCleared, is(false));
-        assertThat(viewModel2.mCleared, is(false));
-        store.clear();
-        assertThat(viewModel1.mCleared, is(true));
-        assertThat(viewModel2.mCleared, is(true));
-        assertThat(store.get("a"), nullValue());
-        assertThat(store.get("b"), nullValue());
-    }
-
-    static class TestViewModel extends ViewModel {
-        boolean mCleared = false;
-
-        @Override
-        protected void onCleared() {
-            mCleared = true;
-        }
-    }
-}
diff --git a/lifecycle/viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java b/lifecycle/viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
new file mode 100644
index 0000000..e63c3e4
--- /dev/null
+++ b/lifecycle/viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 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 androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModelProvider.NewInstanceFactory;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ViewModelProviderTest {
+
+    private ViewModelProvider mViewModelProvider;
+
+    @Before
+    public void setup() {
+        mViewModelProvider = new ViewModelProvider(new ViewModelStore(), new NewInstanceFactory());
+    }
+
+    @Test
+    public void twoViewModelsWithSameKey() throws Throwable {
+        String key = "the_key";
+        ViewModel1 vm1 = mViewModelProvider.get(key, ViewModel1.class);
+        assertThat(vm1.mCleared, is(false));
+        ViewModel2 vw2 = mViewModelProvider.get(key, ViewModel2.class);
+        assertThat(vw2, notNullValue());
+        assertThat(vm1.mCleared, is(true));
+    }
+
+
+    @Test
+    public void localViewModel() throws Throwable {
+        class VM extends ViewModel1 {
+        }
+        try {
+            mViewModelProvider.get(VM.class);
+            Assert.fail();
+        } catch (IllegalArgumentException ignored) {
+        }
+    }
+
+    @Test
+    public void twoViewModels() {
+        ViewModel1 model1 = mViewModelProvider.get(ViewModel1.class);
+        ViewModel2 model2 = mViewModelProvider.get(ViewModel2.class);
+        assertThat(mViewModelProvider.get(ViewModel1.class), is(model1));
+        assertThat(mViewModelProvider.get(ViewModel2.class), is(model2));
+    }
+
+    @Test
+    public void testOwnedBy() {
+        final ViewModelStore store = new ViewModelStore();
+        ViewModelStoreOwner owner = new ViewModelStoreOwner() {
+            @NonNull
+            @Override
+            public ViewModelStore getViewModelStore() {
+                return store;
+            }
+        };
+        ViewModelProvider provider = new ViewModelProvider(owner, new NewInstanceFactory());
+        ViewModel1 viewModel = provider.get(ViewModel1.class);
+        assertThat(viewModel, is(provider.get(ViewModel1.class)));
+    }
+
+    public static class ViewModel1 extends ViewModel {
+        boolean mCleared;
+
+        @Override
+        protected void onCleared() {
+            mCleared = true;
+        }
+    }
+
+    public static class ViewModel2 extends ViewModel {
+    }
+}
diff --git a/lifecycle/viewmodel/src/test/java/androidx/lifecycle/ViewModelStoreTest.java b/lifecycle/viewmodel/src/test/java/androidx/lifecycle/ViewModelStoreTest.java
new file mode 100644
index 0000000..0805b76
--- /dev/null
+++ b/lifecycle/viewmodel/src/test/java/androidx/lifecycle/ViewModelStoreTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ViewModelStoreTest {
+
+    @Test
+    public void testClear() {
+        ViewModelStore store = new ViewModelStore();
+        TestViewModel viewModel1 = new TestViewModel();
+        TestViewModel viewModel2 = new TestViewModel();
+        store.put("a", viewModel1);
+        store.put("b", viewModel2);
+        assertThat(viewModel1.mCleared, is(false));
+        assertThat(viewModel2.mCleared, is(false));
+        store.clear();
+        assertThat(viewModel1.mCleared, is(true));
+        assertThat(viewModel2.mCleared, is(true));
+        assertThat(store.get("a"), nullValue());
+        assertThat(store.get("b"), nullValue());
+    }
+
+    static class TestViewModel extends ViewModel {
+        boolean mCleared = false;
+
+        @Override
+        protected void onCleared() {
+            mCleared = true;
+        }
+    }
+}
diff --git a/loader/src/androidTest/java/androidx/loader/app/LoaderInfoTest.java b/loader/src/androidTest/java/androidx/loader/app/LoaderInfoTest.java
index 510553a..ecdf44f 100644
--- a/loader/src/androidTest/java/androidx/loader/app/LoaderInfoTest.java
+++ b/loader/src/androidTest/java/androidx/loader/app/LoaderInfoTest.java
@@ -21,15 +21,15 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LifecycleRegistry;
 import android.content.Context;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 import androidx.loader.app.test.DelayLoader;
 import androidx.loader.app.test.DummyLoaderCallbacks;
 import androidx.loader.app.test.EmptyActivity;
@@ -74,7 +74,12 @@
         assertFalse("isCallbackWaitingForData should be false before setCallback",
                 loaderInfo.isCallbackWaitingForData());
 
-        loaderInfo.setCallback(mActivityRule.getActivity(), loaderCallback);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                loaderInfo.setCallback(mActivityRule.getActivity(), loaderCallback);
+            }
+        });
         assertTrue("isCallbackWaitingForData should be true immediately after setCallback",
                 loaderInfo.isCallbackWaitingForData());
 
diff --git a/loader/src/androidTest/java/androidx/loader/app/LoaderManagerTest.java b/loader/src/androidTest/java/androidx/loader/app/LoaderManagerTest.java
index 8ac2dcf..cd60ee8 100644
--- a/loader/src/androidTest/java/androidx/loader/app/LoaderManagerTest.java
+++ b/loader/src/androidTest/java/androidx/loader/app/LoaderManagerTest.java
@@ -21,11 +21,6 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.ViewModelStore;
-import android.arch.lifecycle.ViewModelStoreOwner;
 import android.content.Context;
 import android.os.Bundle;
 import android.support.test.filters.SmallTest;
@@ -33,6 +28,11 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
+import androidx.lifecycle.ViewModelStoreOwner;
 import androidx.loader.app.test.DelayLoaderCallbacks;
 import androidx.loader.app.test.DummyLoaderCallbacks;
 import androidx.loader.app.test.EmptyActivity;
diff --git a/loader/src/main/java/androidx/loader/app/LoaderManager.java b/loader/src/main/java/androidx/loader/app/LoaderManager.java
index 764de74..8897575 100644
--- a/loader/src/main/java/androidx/loader/app/LoaderManager.java
+++ b/loader/src/main/java/androidx/loader/app/LoaderManager.java
@@ -16,13 +16,13 @@
 
 package androidx.loader.app;
 
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.ViewModelStoreOwner;
 import android.os.Bundle;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.ViewModelStoreOwner;
 import androidx.loader.content.Loader;
 
 import java.io.FileDescriptor;
diff --git a/loader/src/main/java/androidx/loader/app/LoaderManagerImpl.java b/loader/src/main/java/androidx/loader/app/LoaderManagerImpl.java
index e1f2eee..770779d 100644
--- a/loader/src/main/java/androidx/loader/app/LoaderManagerImpl.java
+++ b/loader/src/main/java/androidx/loader/app/LoaderManagerImpl.java
@@ -16,12 +16,6 @@
 
 package androidx.loader.app;
 
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.MutableLiveData;
-import android.arch.lifecycle.Observer;
-import android.arch.lifecycle.ViewModel;
-import android.arch.lifecycle.ViewModelProvider;
-import android.arch.lifecycle.ViewModelStore;
 import android.os.Bundle;
 import android.os.Looper;
 import android.util.Log;
@@ -31,6 +25,12 @@
 import androidx.annotation.Nullable;
 import androidx.collection.SparseArrayCompat;
 import androidx.core.util.DebugUtils;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
 import androidx.loader.content.Loader;
 
 import java.io.FileDescriptor;
@@ -130,7 +130,7 @@
         }
 
         @Override
-        public void removeObserver(@NonNull Observer<D> observer) {
+        public void removeObserver(@NonNull Observer<? super D> observer) {
             super.removeObserver(observer);
             // Clear out our references when the observer is removed to avoid leaking
             mLifecycleOwner = null;
diff --git a/media/api/current.txt b/media/api/current.txt
index 36577fa..d7df744 100644
--- a/media/api/current.txt
+++ b/media/api/current.txt
@@ -1,5 +1,79 @@
 package android.support.v4.media {
 
+  public final class MediaBrowserCompat {
+    ctor public MediaBrowserCompat(android.content.Context, android.content.ComponentName, android.support.v4.media.MediaBrowserCompat.ConnectionCallback, android.os.Bundle);
+    method public void connect();
+    method public void disconnect();
+    method public android.os.Bundle getExtras();
+    method public void getItem(java.lang.String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+    method public java.lang.String getRoot();
+    method public android.content.ComponentName getServiceComponent();
+    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+    method public boolean isConnected();
+    method public void search(java.lang.String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+    method public void sendCustomAction(java.lang.String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.CustomActionCallback);
+    method public void subscribe(java.lang.String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+    method public void subscribe(java.lang.String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+    method public void unsubscribe(java.lang.String);
+    method public void unsubscribe(java.lang.String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+    field public static final java.lang.String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+    field public static final java.lang.String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+    field public static final java.lang.String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+    field public static final java.lang.String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+    field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+    field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+  }
+
+  public static class MediaBrowserCompat.ConnectionCallback {
+    ctor public MediaBrowserCompat.ConnectionCallback();
+    method public void onConnected();
+    method public void onConnectionFailed();
+    method public void onConnectionSuspended();
+  }
+
+  public static abstract class MediaBrowserCompat.CustomActionCallback {
+    ctor public MediaBrowserCompat.CustomActionCallback();
+    method public void onError(java.lang.String, android.os.Bundle, android.os.Bundle);
+    method public void onProgressUpdate(java.lang.String, android.os.Bundle, android.os.Bundle);
+    method public void onResult(java.lang.String, android.os.Bundle, android.os.Bundle);
+  }
+
+  public static abstract class MediaBrowserCompat.ItemCallback {
+    ctor public MediaBrowserCompat.ItemCallback();
+    method public void onError(java.lang.String);
+    method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem);
+  }
+
+  public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+    ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+    method public int describeContents();
+    method public static android.support.v4.media.MediaBrowserCompat.MediaItem fromMediaItem(java.lang.Object);
+    method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem> fromMediaItemList(java.util.List<?>);
+    method public android.support.v4.media.MediaDescriptionCompat getDescription();
+    method public int getFlags();
+    method public java.lang.String getMediaId();
+    method public boolean isBrowsable();
+    method public boolean isPlayable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem> CREATOR;
+    field public static final int FLAG_BROWSABLE = 1; // 0x1
+    field public static final int FLAG_PLAYABLE = 2; // 0x2
+  }
+
+  public static abstract class MediaBrowserCompat.SearchCallback {
+    ctor public MediaBrowserCompat.SearchCallback();
+    method public void onError(java.lang.String, android.os.Bundle);
+    method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>);
+  }
+
+  public static abstract class MediaBrowserCompat.SubscriptionCallback {
+    ctor public MediaBrowserCompat.SubscriptionCallback();
+    method public void onChildrenLoaded(java.lang.String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>);
+    method public void onChildrenLoaded(java.lang.String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>, android.os.Bundle);
+    method public void onError(java.lang.String);
+    method public void onError(java.lang.String, android.os.Bundle);
+  }
+
   public final class MediaDescriptionCompat implements android.os.Parcelable {
     method public int describeContents();
     method public static android.support.v4.media.MediaDescriptionCompat fromMediaDescription(java.lang.Object);
@@ -131,12 +205,101 @@
 
 package android.support.v4.media.session {
 
+  public final class MediaControllerCompat {
+    ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat);
+    ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token) throws android.os.RemoteException;
+    method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat);
+    method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat, int);
+    method public void adjustVolume(int, int);
+    method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
+    method public android.os.Bundle getExtras();
+    method public long getFlags();
+    method public static android.support.v4.media.session.MediaControllerCompat getMediaController(android.app.Activity);
+    method public java.lang.Object getMediaController();
+    method public android.support.v4.media.MediaMetadataCompat getMetadata();
+    method public java.lang.String getPackageName();
+    method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo getPlaybackInfo();
+    method public android.support.v4.media.session.PlaybackStateCompat getPlaybackState();
+    method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem> getQueue();
+    method public java.lang.CharSequence getQueueTitle();
+    method public int getRatingType();
+    method public int getRepeatMode();
+    method public android.app.PendingIntent getSessionActivity();
+    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+    method public int getShuffleMode();
+    method public android.support.v4.media.session.MediaControllerCompat.TransportControls getTransportControls();
+    method public boolean isCaptioningEnabled();
+    method public boolean isSessionReady();
+    method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+    method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler);
+    method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat);
+    method public deprecated void removeQueueItemAt(int);
+    method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat);
+    method public void setVolumeTo(int, int);
+    method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+  }
+
+  public static abstract class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+    ctor public MediaControllerCompat.Callback();
+    method public void binderDied();
+    method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo);
+    method public void onCaptioningEnabledChanged(boolean);
+    method public void onExtrasChanged(android.os.Bundle);
+    method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat);
+    method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat);
+    method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>);
+    method public void onQueueTitleChanged(java.lang.CharSequence);
+    method public void onRepeatModeChanged(int);
+    method public void onSessionDestroyed();
+    method public void onSessionEvent(java.lang.String, android.os.Bundle);
+    method public void onSessionReady();
+    method public void onShuffleModeChanged(int);
+  }
+
+  public static final class MediaControllerCompat.PlaybackInfo {
+    method public int getAudioStream();
+    method public int getCurrentVolume();
+    method public int getMaxVolume();
+    method public int getPlaybackType();
+    method public int getVolumeControl();
+    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+  }
+
+  public static abstract class MediaControllerCompat.TransportControls {
+    method public abstract void fastForward();
+    method public abstract void pause();
+    method public abstract void play();
+    method public abstract void playFromMediaId(java.lang.String, android.os.Bundle);
+    method public abstract void playFromSearch(java.lang.String, android.os.Bundle);
+    method public abstract void playFromUri(android.net.Uri, android.os.Bundle);
+    method public abstract void prepare();
+    method public abstract void prepareFromMediaId(java.lang.String, android.os.Bundle);
+    method public abstract void prepareFromSearch(java.lang.String, android.os.Bundle);
+    method public abstract void prepareFromUri(android.net.Uri, android.os.Bundle);
+    method public abstract void rewind();
+    method public abstract void seekTo(long);
+    method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction, android.os.Bundle);
+    method public abstract void sendCustomAction(java.lang.String, android.os.Bundle);
+    method public abstract void setCaptioningEnabled(boolean);
+    method public abstract void setRating(android.support.v4.media.RatingCompat);
+    method public abstract void setRating(android.support.v4.media.RatingCompat, android.os.Bundle);
+    method public abstract void setRepeatMode(int);
+    method public abstract void setShuffleMode(int);
+    method public abstract void skipToNext();
+    method public abstract void skipToPrevious();
+    method public abstract void skipToQueueItem(long);
+    method public abstract void stop();
+    field public static final java.lang.String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+  }
+
   public class MediaSessionCompat {
     ctor public MediaSessionCompat(android.content.Context, java.lang.String);
     ctor public MediaSessionCompat(android.content.Context, java.lang.String, android.content.ComponentName, android.app.PendingIntent);
     method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener);
     method public static android.support.v4.media.session.MediaSessionCompat fromMediaSession(android.content.Context, java.lang.Object);
-    method public androidx.media.session.MediaControllerCompat getController();
+    method public android.support.v4.media.session.MediaControllerCompat getController();
     method public java.lang.Object getMediaSession();
     method public java.lang.Object getRemoteControlClient();
     method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
@@ -402,80 +565,6 @@
     method public androidx.media.AudioAttributesCompat.Builder setUsage(int);
   }
 
-  public final class MediaBrowserCompat {
-    ctor public MediaBrowserCompat(android.content.Context, android.content.ComponentName, androidx.media.MediaBrowserCompat.ConnectionCallback, android.os.Bundle);
-    method public void connect();
-    method public void disconnect();
-    method public android.os.Bundle getExtras();
-    method public void getItem(java.lang.String, androidx.media.MediaBrowserCompat.ItemCallback);
-    method public java.lang.String getRoot();
-    method public android.content.ComponentName getServiceComponent();
-    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
-    method public boolean isConnected();
-    method public void search(java.lang.String, android.os.Bundle, androidx.media.MediaBrowserCompat.SearchCallback);
-    method public void sendCustomAction(java.lang.String, android.os.Bundle, androidx.media.MediaBrowserCompat.CustomActionCallback);
-    method public void subscribe(java.lang.String, androidx.media.MediaBrowserCompat.SubscriptionCallback);
-    method public void subscribe(java.lang.String, android.os.Bundle, androidx.media.MediaBrowserCompat.SubscriptionCallback);
-    method public void unsubscribe(java.lang.String);
-    method public void unsubscribe(java.lang.String, androidx.media.MediaBrowserCompat.SubscriptionCallback);
-    field public static final java.lang.String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
-    field public static final java.lang.String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
-    field public static final java.lang.String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
-    field public static final java.lang.String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
-    field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
-    field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
-  }
-
-  public static class MediaBrowserCompat.ConnectionCallback {
-    ctor public MediaBrowserCompat.ConnectionCallback();
-    method public void onConnected();
-    method public void onConnectionFailed();
-    method public void onConnectionSuspended();
-  }
-
-  public static abstract class MediaBrowserCompat.CustomActionCallback {
-    ctor public MediaBrowserCompat.CustomActionCallback();
-    method public void onError(java.lang.String, android.os.Bundle, android.os.Bundle);
-    method public void onProgressUpdate(java.lang.String, android.os.Bundle, android.os.Bundle);
-    method public void onResult(java.lang.String, android.os.Bundle, android.os.Bundle);
-  }
-
-  public static abstract class MediaBrowserCompat.ItemCallback {
-    ctor public MediaBrowserCompat.ItemCallback();
-    method public void onError(java.lang.String);
-    method public void onItemLoaded(androidx.media.MediaBrowserCompat.MediaItem);
-  }
-
-  public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
-    ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
-    method public int describeContents();
-    method public static androidx.media.MediaBrowserCompat.MediaItem fromMediaItem(java.lang.Object);
-    method public static java.util.List<androidx.media.MediaBrowserCompat.MediaItem> fromMediaItemList(java.util.List<?>);
-    method public android.support.v4.media.MediaDescriptionCompat getDescription();
-    method public int getFlags();
-    method public java.lang.String getMediaId();
-    method public boolean isBrowsable();
-    method public boolean isPlayable();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.media.MediaBrowserCompat.MediaItem> CREATOR;
-    field public static final int FLAG_BROWSABLE = 1; // 0x1
-    field public static final int FLAG_PLAYABLE = 2; // 0x2
-  }
-
-  public static abstract class MediaBrowserCompat.SearchCallback {
-    ctor public MediaBrowserCompat.SearchCallback();
-    method public void onError(java.lang.String, android.os.Bundle);
-    method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<androidx.media.MediaBrowserCompat.MediaItem>);
-  }
-
-  public static abstract class MediaBrowserCompat.SubscriptionCallback {
-    ctor public MediaBrowserCompat.SubscriptionCallback();
-    method public void onChildrenLoaded(java.lang.String, java.util.List<androidx.media.MediaBrowserCompat.MediaItem>);
-    method public void onChildrenLoaded(java.lang.String, java.util.List<androidx.media.MediaBrowserCompat.MediaItem>, android.os.Bundle);
-    method public void onError(java.lang.String);
-    method public void onError(java.lang.String, android.os.Bundle);
-  }
-
   public abstract class MediaBrowserServiceCompat extends android.app.Service {
     ctor public MediaBrowserServiceCompat();
     method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
@@ -486,10 +575,10 @@
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onCustomAction(java.lang.String, android.os.Bundle, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle>);
     method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
-    method public abstract void onLoadChildren(java.lang.String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<androidx.media.MediaBrowserCompat.MediaItem>>);
-    method public void onLoadChildren(java.lang.String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<androidx.media.MediaBrowserCompat.MediaItem>>, android.os.Bundle);
-    method public void onLoadItem(java.lang.String, androidx.media.MediaBrowserServiceCompat.Result<androidx.media.MediaBrowserCompat.MediaItem>);
-    method public void onSearch(java.lang.String, android.os.Bundle, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<androidx.media.MediaBrowserCompat.MediaItem>>);
+    method public abstract void onLoadChildren(java.lang.String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>);
+    method public void onLoadChildren(java.lang.String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>, android.os.Bundle);
+    method public void onLoadItem(java.lang.String, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem>);
+    method public void onSearch(java.lang.String, android.os.Bundle, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>);
     method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token);
     field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
   }
@@ -564,94 +653,5 @@
     method public void onReceive(android.content.Context, android.content.Intent);
   }
 
-  public final class MediaControllerCompat {
-    ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat);
-    ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token) throws android.os.RemoteException;
-    method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat);
-    method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat, int);
-    method public void adjustVolume(int, int);
-    method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
-    method public android.os.Bundle getExtras();
-    method public long getFlags();
-    method public static androidx.media.session.MediaControllerCompat getMediaController(android.app.Activity);
-    method public java.lang.Object getMediaController();
-    method public android.support.v4.media.MediaMetadataCompat getMetadata();
-    method public java.lang.String getPackageName();
-    method public androidx.media.session.MediaControllerCompat.PlaybackInfo getPlaybackInfo();
-    method public android.support.v4.media.session.PlaybackStateCompat getPlaybackState();
-    method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem> getQueue();
-    method public java.lang.CharSequence getQueueTitle();
-    method public int getRatingType();
-    method public int getRepeatMode();
-    method public android.app.PendingIntent getSessionActivity();
-    method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
-    method public int getShuffleMode();
-    method public androidx.media.session.MediaControllerCompat.TransportControls getTransportControls();
-    method public boolean isCaptioningEnabled();
-    method public boolean isSessionReady();
-    method public void registerCallback(androidx.media.session.MediaControllerCompat.Callback);
-    method public void registerCallback(androidx.media.session.MediaControllerCompat.Callback, android.os.Handler);
-    method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat);
-    method public deprecated void removeQueueItemAt(int);
-    method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
-    method public static void setMediaController(android.app.Activity, androidx.media.session.MediaControllerCompat);
-    method public void setVolumeTo(int, int);
-    method public void unregisterCallback(androidx.media.session.MediaControllerCompat.Callback);
-  }
-
-  public static abstract class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
-    ctor public MediaControllerCompat.Callback();
-    method public void binderDied();
-    method public void onAudioInfoChanged(androidx.media.session.MediaControllerCompat.PlaybackInfo);
-    method public void onCaptioningEnabledChanged(boolean);
-    method public void onExtrasChanged(android.os.Bundle);
-    method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat);
-    method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat);
-    method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>);
-    method public void onQueueTitleChanged(java.lang.CharSequence);
-    method public void onRepeatModeChanged(int);
-    method public void onSessionDestroyed();
-    method public void onSessionEvent(java.lang.String, android.os.Bundle);
-    method public void onSessionReady();
-    method public void onShuffleModeChanged(int);
-  }
-
-  public static final class MediaControllerCompat.PlaybackInfo {
-    method public int getAudioStream();
-    method public int getCurrentVolume();
-    method public int getMaxVolume();
-    method public int getPlaybackType();
-    method public int getVolumeControl();
-    field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
-    field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
-  }
-
-  public static abstract class MediaControllerCompat.TransportControls {
-    method public abstract void fastForward();
-    method public abstract void pause();
-    method public abstract void play();
-    method public abstract void playFromMediaId(java.lang.String, android.os.Bundle);
-    method public abstract void playFromSearch(java.lang.String, android.os.Bundle);
-    method public abstract void playFromUri(android.net.Uri, android.os.Bundle);
-    method public abstract void prepare();
-    method public abstract void prepareFromMediaId(java.lang.String, android.os.Bundle);
-    method public abstract void prepareFromSearch(java.lang.String, android.os.Bundle);
-    method public abstract void prepareFromUri(android.net.Uri, android.os.Bundle);
-    method public abstract void rewind();
-    method public abstract void seekTo(long);
-    method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction, android.os.Bundle);
-    method public abstract void sendCustomAction(java.lang.String, android.os.Bundle);
-    method public abstract void setCaptioningEnabled(boolean);
-    method public abstract void setRating(android.support.v4.media.RatingCompat);
-    method public abstract void setRating(android.support.v4.media.RatingCompat, android.os.Bundle);
-    method public abstract void setRepeatMode(int);
-    method public abstract void setShuffleMode(int);
-    method public abstract void skipToNext();
-    method public abstract void skipToPrevious();
-    method public abstract void skipToQueueItem(long);
-    method public abstract void stop();
-    field public static final java.lang.String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
-  }
-
 }
 
diff --git a/media/api21/android/support/v4/media/MediaBrowserCompatApi21.java b/media/api21/android/support/v4/media/MediaBrowserCompatApi21.java
new file mode 100644
index 0000000..4f040df
--- /dev/null
+++ b/media/api21/android/support/v4/media/MediaBrowserCompatApi21.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018 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.support.v4.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.List;
+
+@RequiresApi(21)
+class MediaBrowserCompatApi21 {
+    static final String NULL_MEDIA_ITEM_ID =
+            "android.support.v4.media.MediaBrowserCompat.NULL_MEDIA_ITEM";
+
+    public static Object createConnectionCallback(ConnectionCallback callback) {
+        return new ConnectionCallbackProxy<>(callback);
+    }
+
+    public static Object createBrowser(Context context, ComponentName serviceComponent,
+            Object callback, Bundle rootHints) {
+        return new MediaBrowser(context, serviceComponent,
+                (MediaBrowser.ConnectionCallback) callback, rootHints);
+    }
+
+    public static void connect(Object browserObj) {
+        ((MediaBrowser)browserObj).connect();
+    }
+
+    public static void disconnect(Object browserObj) {
+        ((MediaBrowser)browserObj).disconnect();
+
+    }
+
+    public static boolean isConnected(Object browserObj) {
+        return ((MediaBrowser)browserObj).isConnected();
+    }
+
+    public static ComponentName getServiceComponent(Object browserObj) {
+        return ((MediaBrowser)browserObj).getServiceComponent();
+    }
+
+    public static String getRoot(Object browserObj) {
+        return ((MediaBrowser)browserObj).getRoot();
+    }
+
+    public static Bundle getExtras(Object browserObj) {
+        return ((MediaBrowser)browserObj).getExtras();
+    }
+
+    public static Object getSessionToken(Object browserObj) {
+        return ((MediaBrowser)browserObj).getSessionToken();
+    }
+
+    public static Object createSubscriptionCallback(SubscriptionCallback callback) {
+        return new SubscriptionCallbackProxy<>(callback);
+    }
+
+    public static void subscribe(
+            Object browserObj, String parentId, Object subscriptionCallbackObj) {
+        ((MediaBrowser)browserObj).subscribe(parentId,
+                (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
+    }
+
+    public static void unsubscribe(Object browserObj, String parentId) {
+        ((MediaBrowser)browserObj).unsubscribe(parentId);
+    }
+
+    interface ConnectionCallback {
+        void onConnected();
+        void onConnectionSuspended();
+        void onConnectionFailed();
+    }
+
+    static class ConnectionCallbackProxy<T extends ConnectionCallback>
+            extends MediaBrowser.ConnectionCallback {
+        protected final T mConnectionCallback;
+
+        public ConnectionCallbackProxy(T connectionCallback) {
+            mConnectionCallback = connectionCallback;
+        }
+
+        @Override
+        public void onConnected() {
+            mConnectionCallback.onConnected();
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            mConnectionCallback.onConnectionSuspended();
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            mConnectionCallback.onConnectionFailed();
+        }
+    }
+
+    interface SubscriptionCallback {
+        void onChildrenLoaded(@NonNull String parentId, List<?> children);
+        void onError(@NonNull String parentId);
+    }
+
+    static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
+            extends MediaBrowser.SubscriptionCallback {
+        protected final T mSubscriptionCallback;
+
+        public SubscriptionCallbackProxy(T callback) {
+            mSubscriptionCallback = callback;
+        }
+
+        @Override
+        public void onChildrenLoaded(@NonNull String parentId,
+                List<MediaBrowser.MediaItem> children) {
+            mSubscriptionCallback.onChildrenLoaded(parentId, children);
+        }
+
+        @Override
+        public void onError(@NonNull String parentId) {
+            mSubscriptionCallback.onError(parentId);
+        }
+    }
+
+    static class MediaItem {
+
+        public static int getFlags(Object itemObj) {
+            return ((MediaBrowser.MediaItem) itemObj).getFlags();
+        }
+
+        public static Object getDescription(Object itemObj) {
+            return ((MediaBrowser.MediaItem) itemObj).getDescription();
+        }
+    }
+}
diff --git a/media/api21/android/support/v4/media/session/MediaControllerCompatApi21.java b/media/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
new file mode 100644
index 0000000..6c5f860
--- /dev/null
+++ b/media/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2018 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.support.v4.media.session;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+import androidx.annotation.RequiresApi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RequiresApi(21)
+class MediaControllerCompatApi21 {
+    public static Object fromToken(Context context, Object sessionToken) {
+        return new MediaController(context, (MediaSession.Token) sessionToken);
+    }
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static void registerCallback(Object controllerObj, Object callbackObj, Handler handler) {
+        ((MediaController) controllerObj).registerCallback(
+                (MediaController.Callback)callbackObj, handler);
+    }
+
+    public static void unregisterCallback(Object controllerObj, Object callbackObj) {
+        ((MediaController) controllerObj)
+                .unregisterCallback((MediaController.Callback) callbackObj);
+    }
+
+    public static void setMediaController(Activity activity, Object controllerObj) {
+        activity.setMediaController((MediaController) controllerObj);
+    }
+
+    public static Object getMediaController(Activity activity) {
+        return activity.getMediaController();
+    }
+
+    public static Object getSessionToken(Object controllerObj) {
+        return ((MediaController) controllerObj).getSessionToken();
+    }
+
+    public static Object getTransportControls(Object controllerObj) {
+        return ((MediaController)controllerObj).getTransportControls();
+    }
+
+    public static Object getPlaybackState(Object controllerObj) {
+        return ((MediaController)controllerObj).getPlaybackState();
+    }
+
+    public static Object getMetadata(Object controllerObj) {
+        return ((MediaController)controllerObj).getMetadata();
+    }
+
+    public static List<Object> getQueue(Object controllerObj) {
+        List<MediaSession.QueueItem> queue = ((MediaController) controllerObj).getQueue();
+        if (queue == null) {
+            return null;
+        }
+        List<Object> queueObjs = new ArrayList<Object>(queue);
+        return queueObjs;
+    }
+
+    public static CharSequence getQueueTitle(Object controllerObj) {
+        return ((MediaController) controllerObj).getQueueTitle();
+    }
+
+    public static Bundle getExtras(Object controllerObj) {
+        return ((MediaController) controllerObj).getExtras();
+    }
+
+    public static int getRatingType(Object controllerObj) {
+        return ((MediaController) controllerObj).getRatingType();
+    }
+
+    public static long getFlags(Object controllerObj) {
+        return ((MediaController) controllerObj).getFlags();
+    }
+
+    public static Object getPlaybackInfo(Object controllerObj) {
+        return ((MediaController) controllerObj).getPlaybackInfo();
+    }
+
+    public static PendingIntent getSessionActivity(Object controllerObj) {
+        return ((MediaController) controllerObj).getSessionActivity();
+    }
+
+    public static boolean dispatchMediaButtonEvent(Object controllerObj, KeyEvent event) {
+        return ((MediaController) controllerObj).dispatchMediaButtonEvent(event);
+    }
+
+    public static void setVolumeTo(Object controllerObj, int value, int flags) {
+        ((MediaController) controllerObj).setVolumeTo(value, flags);
+    }
+
+    public static void adjustVolume(Object controllerObj, int direction, int flags) {
+        ((MediaController) controllerObj).adjustVolume(direction, flags);
+    }
+
+    public static void sendCommand(Object controllerObj,
+            String command, Bundle params, ResultReceiver cb) {
+        ((MediaController) controllerObj).sendCommand(command, params, cb);
+    }
+
+    public static String getPackageName(Object controllerObj) {
+        return ((MediaController) controllerObj).getPackageName();
+    }
+
+    public static class TransportControls {
+        public static void play(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).play();
+        }
+
+        public static void pause(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).pause();
+        }
+
+        public static void stop(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).stop();
+        }
+
+        public static void seekTo(Object controlsObj, long pos) {
+            ((MediaController.TransportControls)controlsObj).seekTo(pos);
+        }
+
+        public static void fastForward(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).fastForward();
+        }
+
+        public static void rewind(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).rewind();
+        }
+
+        public static void skipToNext(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).skipToNext();
+        }
+
+        public static void skipToPrevious(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).skipToPrevious();
+        }
+
+        public static void setRating(Object controlsObj, Object ratingObj) {
+            ((MediaController.TransportControls)controlsObj).setRating((Rating)ratingObj);
+        }
+
+        public static void playFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).playFromMediaId(mediaId, extras);
+        }
+
+        public static void playFromSearch(Object controlsObj, String query, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).playFromSearch(query, extras);
+        }
+
+        public static void skipToQueueItem(Object controlsObj, long id) {
+            ((MediaController.TransportControls) controlsObj).skipToQueueItem(id);
+        }
+
+        public static void sendCustomAction(Object controlsObj, String action, Bundle args) {
+            ((MediaController.TransportControls) controlsObj).sendCustomAction(action, args);
+        }
+    }
+
+    public static class PlaybackInfo {
+        public static int getPlaybackType(Object volumeInfoObj) {
+            return ((MediaController.PlaybackInfo)volumeInfoObj).getPlaybackType();
+        }
+
+        public static AudioAttributes getAudioAttributes(Object volumeInfoObj) {
+            return ((MediaController.PlaybackInfo) volumeInfoObj).getAudioAttributes();
+        }
+
+        public static int getLegacyAudioStream(Object volumeInfoObj) {
+            AudioAttributes attrs = getAudioAttributes(volumeInfoObj);
+            return toLegacyStreamType(attrs);
+        }
+
+        public static int getVolumeControl(Object volumeInfoObj) {
+            return ((MediaController.PlaybackInfo)volumeInfoObj).getVolumeControl();
+        }
+
+        public static int getMaxVolume(Object volumeInfoObj) {
+            return ((MediaController.PlaybackInfo)volumeInfoObj).getMaxVolume();
+        }
+
+        public static int getCurrentVolume(Object volumeInfoObj) {
+            return ((MediaController.PlaybackInfo)volumeInfoObj).getCurrentVolume();
+        }
+
+        // This is copied from AudioAttributes.toLegacyStreamType. TODO This
+        // either needs to be kept in sync with that one or toLegacyStreamType
+        // needs to be made public so it can be used by the support lib.
+        private static final int FLAG_SCO = 0x1 << 2;
+        private static final int STREAM_BLUETOOTH_SCO = 6;
+        private static final int STREAM_SYSTEM_ENFORCED = 7;
+        private static int toLegacyStreamType(AudioAttributes aa) {
+            // flags to stream type mapping
+            if ((aa.getFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+                    == AudioAttributes.FLAG_AUDIBILITY_ENFORCED) {
+                return STREAM_SYSTEM_ENFORCED;
+            }
+            if ((aa.getFlags() & FLAG_SCO) == FLAG_SCO) {
+                return STREAM_BLUETOOTH_SCO;
+            }
+
+            // usage to stream type mapping
+            switch (aa.getUsage()) {
+                case AudioAttributes.USAGE_MEDIA:
+                case AudioAttributes.USAGE_GAME:
+                case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+                case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+                    return AudioManager.STREAM_MUSIC;
+                case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+                    return AudioManager.STREAM_SYSTEM;
+                case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+                    return AudioManager.STREAM_VOICE_CALL;
+                case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+                    return AudioManager.STREAM_DTMF;
+                case AudioAttributes.USAGE_ALARM:
+                    return AudioManager.STREAM_ALARM;
+                case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+                    return AudioManager.STREAM_RING;
+                case AudioAttributes.USAGE_NOTIFICATION:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+                case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+                    return AudioManager.STREAM_NOTIFICATION;
+                case AudioAttributes.USAGE_UNKNOWN:
+                default:
+                    return AudioManager.STREAM_MUSIC;
+            }
+        }
+    }
+
+    public static interface Callback {
+        public void onSessionDestroyed();
+        public void onSessionEvent(String event, Bundle extras);
+        public void onPlaybackStateChanged(Object stateObj);
+        public void onMetadataChanged(Object metadataObj);
+        public void onQueueChanged(List<?> queue);
+        public void onQueueTitleChanged(CharSequence title);
+        public void onExtrasChanged(Bundle extras);
+        public void onAudioInfoChanged(int type, int stream, int control, int max, int current);
+    }
+
+    static class CallbackProxy<T extends Callback> extends MediaController.Callback {
+        protected final T mCallback;
+
+        public CallbackProxy(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            mCallback.onSessionDestroyed();
+        }
+
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            mCallback.onSessionEvent(event, extras);
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            mCallback.onPlaybackStateChanged(state);
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            mCallback.onMetadataChanged(metadata);
+        }
+
+        @Override
+        public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+            mCallback.onQueueChanged(queue);
+        }
+
+        @Override
+        public void onQueueTitleChanged(CharSequence title) {
+            mCallback.onQueueTitleChanged(title);
+        }
+
+        @Override
+        public void onExtrasChanged(Bundle extras) {
+            mCallback.onExtrasChanged(extras);
+        }
+
+        @Override
+        public void onAudioInfoChanged(MediaController.PlaybackInfo info){
+            mCallback.onAudioInfoChanged(info.getPlaybackType(),
+                    PlaybackInfo.getLegacyAudioStream(info), info.getVolumeControl(),
+                    info.getMaxVolume(), info.getCurrentVolume());
+        }
+    }
+}
diff --git a/media/api21/androidx/media/MediaBrowserCompatApi21.java b/media/api21/androidx/media/MediaBrowserCompatApi21.java
deleted file mode 100644
index 842f54a..0000000
--- a/media/api21/androidx/media/MediaBrowserCompatApi21.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.util.List;
-
-@RequiresApi(21)
-class MediaBrowserCompatApi21 {
-    static final String NULL_MEDIA_ITEM_ID =
-            "androidx.media.MediaBrowserCompat.NULL_MEDIA_ITEM";
-
-    public static Object createConnectionCallback(ConnectionCallback callback) {
-        return new ConnectionCallbackProxy<>(callback);
-    }
-
-    public static Object createBrowser(Context context, ComponentName serviceComponent,
-            Object callback, Bundle rootHints) {
-        return new MediaBrowser(context, serviceComponent,
-                (MediaBrowser.ConnectionCallback) callback, rootHints);
-    }
-
-    public static void connect(Object browserObj) {
-        ((MediaBrowser)browserObj).connect();
-    }
-
-    public static void disconnect(Object browserObj) {
-        ((MediaBrowser)browserObj).disconnect();
-
-    }
-
-    public static boolean isConnected(Object browserObj) {
-        return ((MediaBrowser)browserObj).isConnected();
-    }
-
-    public static ComponentName getServiceComponent(Object browserObj) {
-        return ((MediaBrowser)browserObj).getServiceComponent();
-    }
-
-    public static String getRoot(Object browserObj) {
-        return ((MediaBrowser)browserObj).getRoot();
-    }
-
-    public static Bundle getExtras(Object browserObj) {
-        return ((MediaBrowser)browserObj).getExtras();
-    }
-
-    public static Object getSessionToken(Object browserObj) {
-        return ((MediaBrowser)browserObj).getSessionToken();
-    }
-
-    public static Object createSubscriptionCallback(SubscriptionCallback callback) {
-        return new SubscriptionCallbackProxy<>(callback);
-    }
-
-    public static void subscribe(
-            Object browserObj, String parentId, Object subscriptionCallbackObj) {
-        ((MediaBrowser)browserObj).subscribe(parentId,
-                (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
-    }
-
-    public static void unsubscribe(Object browserObj, String parentId) {
-        ((MediaBrowser)browserObj).unsubscribe(parentId);
-    }
-
-    interface ConnectionCallback {
-        void onConnected();
-        void onConnectionSuspended();
-        void onConnectionFailed();
-    }
-
-    static class ConnectionCallbackProxy<T extends ConnectionCallback>
-            extends MediaBrowser.ConnectionCallback {
-        protected final T mConnectionCallback;
-
-        public ConnectionCallbackProxy(T connectionCallback) {
-            mConnectionCallback = connectionCallback;
-        }
-
-        @Override
-        public void onConnected() {
-            mConnectionCallback.onConnected();
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            mConnectionCallback.onConnectionSuspended();
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            mConnectionCallback.onConnectionFailed();
-        }
-    }
-
-    interface SubscriptionCallback {
-        void onChildrenLoaded(@NonNull String parentId, List<?> children);
-        void onError(@NonNull String parentId);
-    }
-
-    static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
-            extends MediaBrowser.SubscriptionCallback {
-        protected final T mSubscriptionCallback;
-
-        public SubscriptionCallbackProxy(T callback) {
-            mSubscriptionCallback = callback;
-        }
-
-        @Override
-        public void onChildrenLoaded(@NonNull String parentId,
-                List<MediaBrowser.MediaItem> children) {
-            mSubscriptionCallback.onChildrenLoaded(parentId, children);
-        }
-
-        @Override
-        public void onError(@NonNull String parentId) {
-            mSubscriptionCallback.onError(parentId);
-        }
-    }
-
-    static class MediaItem {
-
-        public static int getFlags(Object itemObj) {
-            return ((MediaBrowser.MediaItem) itemObj).getFlags();
-        }
-
-        public static Object getDescription(Object itemObj) {
-            return ((MediaBrowser.MediaItem) itemObj).getDescription();
-        }
-    }
-}
diff --git a/media/api21/androidx/media/session/MediaControllerCompatApi21.java b/media/api21/androidx/media/session/MediaControllerCompatApi21.java
deleted file mode 100644
index 9da2b83..0000000
--- a/media/api21/androidx/media/session/MediaControllerCompatApi21.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media.session;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.MediaMetadata;
-import android.media.Rating;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.view.KeyEvent;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RequiresApi(21)
-class MediaControllerCompatApi21 {
-    public static Object fromToken(Context context, Object sessionToken) {
-        return new MediaController(context, (MediaSession.Token) sessionToken);
-    }
-
-    public static Object createCallback(Callback callback) {
-        return new CallbackProxy<Callback>(callback);
-    }
-
-    public static void registerCallback(Object controllerObj, Object callbackObj, Handler handler) {
-        ((MediaController) controllerObj).registerCallback(
-                (MediaController.Callback)callbackObj, handler);
-    }
-
-    public static void unregisterCallback(Object controllerObj, Object callbackObj) {
-        ((MediaController) controllerObj)
-                .unregisterCallback((MediaController.Callback) callbackObj);
-    }
-
-    public static void setMediaController(Activity activity, Object controllerObj) {
-        activity.setMediaController((MediaController) controllerObj);
-    }
-
-    public static Object getMediaController(Activity activity) {
-        return activity.getMediaController();
-    }
-
-    public static Object getSessionToken(Object controllerObj) {
-        return ((MediaController) controllerObj).getSessionToken();
-    }
-
-    public static Object getTransportControls(Object controllerObj) {
-        return ((MediaController)controllerObj).getTransportControls();
-    }
-
-    public static Object getPlaybackState(Object controllerObj) {
-        return ((MediaController)controllerObj).getPlaybackState();
-    }
-
-    public static Object getMetadata(Object controllerObj) {
-        return ((MediaController)controllerObj).getMetadata();
-    }
-
-    public static List<Object> getQueue(Object controllerObj) {
-        List<MediaSession.QueueItem> queue = ((MediaController) controllerObj).getQueue();
-        if (queue == null) {
-            return null;
-        }
-        List<Object> queueObjs = new ArrayList<Object>(queue);
-        return queueObjs;
-    }
-
-    public static CharSequence getQueueTitle(Object controllerObj) {
-        return ((MediaController) controllerObj).getQueueTitle();
-    }
-
-    public static Bundle getExtras(Object controllerObj) {
-        return ((MediaController) controllerObj).getExtras();
-    }
-
-    public static int getRatingType(Object controllerObj) {
-        return ((MediaController) controllerObj).getRatingType();
-    }
-
-    public static long getFlags(Object controllerObj) {
-        return ((MediaController) controllerObj).getFlags();
-    }
-
-    public static Object getPlaybackInfo(Object controllerObj) {
-        return ((MediaController) controllerObj).getPlaybackInfo();
-    }
-
-    public static PendingIntent getSessionActivity(Object controllerObj) {
-        return ((MediaController) controllerObj).getSessionActivity();
-    }
-
-    public static boolean dispatchMediaButtonEvent(Object controllerObj, KeyEvent event) {
-        return ((MediaController) controllerObj).dispatchMediaButtonEvent(event);
-    }
-
-    public static void setVolumeTo(Object controllerObj, int value, int flags) {
-        ((MediaController) controllerObj).setVolumeTo(value, flags);
-    }
-
-    public static void adjustVolume(Object controllerObj, int direction, int flags) {
-        ((MediaController) controllerObj).adjustVolume(direction, flags);
-    }
-
-    public static void sendCommand(Object controllerObj,
-            String command, Bundle params, ResultReceiver cb) {
-        ((MediaController) controllerObj).sendCommand(command, params, cb);
-    }
-
-    public static String getPackageName(Object controllerObj) {
-        return ((MediaController) controllerObj).getPackageName();
-    }
-
-    public static class TransportControls {
-        public static void play(Object controlsObj) {
-            ((MediaController.TransportControls)controlsObj).play();
-        }
-
-        public static void pause(Object controlsObj) {
-            ((MediaController.TransportControls)controlsObj).pause();
-        }
-
-        public static void stop(Object controlsObj) {
-            ((MediaController.TransportControls)controlsObj).stop();
-        }
-
-        public static void seekTo(Object controlsObj, long pos) {
-            ((MediaController.TransportControls)controlsObj).seekTo(pos);
-        }
-
-        public static void fastForward(Object controlsObj) {
-            ((MediaController.TransportControls)controlsObj).fastForward();
-        }
-
-        public static void rewind(Object controlsObj) {
-            ((MediaController.TransportControls)controlsObj).rewind();
-        }
-
-        public static void skipToNext(Object controlsObj) {
-            ((MediaController.TransportControls)controlsObj).skipToNext();
-        }
-
-        public static void skipToPrevious(Object controlsObj) {
-            ((MediaController.TransportControls)controlsObj).skipToPrevious();
-        }
-
-        public static void setRating(Object controlsObj, Object ratingObj) {
-            ((MediaController.TransportControls)controlsObj).setRating((Rating)ratingObj);
-        }
-
-        public static void playFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
-            ((MediaController.TransportControls) controlsObj).playFromMediaId(mediaId, extras);
-        }
-
-        public static void playFromSearch(Object controlsObj, String query, Bundle extras) {
-            ((MediaController.TransportControls) controlsObj).playFromSearch(query, extras);
-        }
-
-        public static void skipToQueueItem(Object controlsObj, long id) {
-            ((MediaController.TransportControls) controlsObj).skipToQueueItem(id);
-        }
-
-        public static void sendCustomAction(Object controlsObj, String action, Bundle args) {
-            ((MediaController.TransportControls) controlsObj).sendCustomAction(action, args);
-        }
-    }
-
-    public static class PlaybackInfo {
-        public static int getPlaybackType(Object volumeInfoObj) {
-            return ((MediaController.PlaybackInfo)volumeInfoObj).getPlaybackType();
-        }
-
-        public static AudioAttributes getAudioAttributes(Object volumeInfoObj) {
-            return ((MediaController.PlaybackInfo) volumeInfoObj).getAudioAttributes();
-        }
-
-        public static int getLegacyAudioStream(Object volumeInfoObj) {
-            AudioAttributes attrs = getAudioAttributes(volumeInfoObj);
-            return toLegacyStreamType(attrs);
-        }
-
-        public static int getVolumeControl(Object volumeInfoObj) {
-            return ((MediaController.PlaybackInfo)volumeInfoObj).getVolumeControl();
-        }
-
-        public static int getMaxVolume(Object volumeInfoObj) {
-            return ((MediaController.PlaybackInfo)volumeInfoObj).getMaxVolume();
-        }
-
-        public static int getCurrentVolume(Object volumeInfoObj) {
-            return ((MediaController.PlaybackInfo)volumeInfoObj).getCurrentVolume();
-        }
-
-        // This is copied from AudioAttributes.toLegacyStreamType. TODO This
-        // either needs to be kept in sync with that one or toLegacyStreamType
-        // needs to be made public so it can be used by the support lib.
-        private static final int FLAG_SCO = 0x1 << 2;
-        private static final int STREAM_BLUETOOTH_SCO = 6;
-        private static final int STREAM_SYSTEM_ENFORCED = 7;
-        private static int toLegacyStreamType(AudioAttributes aa) {
-            // flags to stream type mapping
-            if ((aa.getFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
-                    == AudioAttributes.FLAG_AUDIBILITY_ENFORCED) {
-                return STREAM_SYSTEM_ENFORCED;
-            }
-            if ((aa.getFlags() & FLAG_SCO) == FLAG_SCO) {
-                return STREAM_BLUETOOTH_SCO;
-            }
-
-            // usage to stream type mapping
-            switch (aa.getUsage()) {
-                case AudioAttributes.USAGE_MEDIA:
-                case AudioAttributes.USAGE_GAME:
-                case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
-                case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
-                    return AudioManager.STREAM_MUSIC;
-                case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
-                    return AudioManager.STREAM_SYSTEM;
-                case AudioAttributes.USAGE_VOICE_COMMUNICATION:
-                    return AudioManager.STREAM_VOICE_CALL;
-                case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
-                    return AudioManager.STREAM_DTMF;
-                case AudioAttributes.USAGE_ALARM:
-                    return AudioManager.STREAM_ALARM;
-                case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
-                    return AudioManager.STREAM_RING;
-                case AudioAttributes.USAGE_NOTIFICATION:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
-                case AudioAttributes.USAGE_NOTIFICATION_EVENT:
-                    return AudioManager.STREAM_NOTIFICATION;
-                case AudioAttributes.USAGE_UNKNOWN:
-                default:
-                    return AudioManager.STREAM_MUSIC;
-            }
-        }
-    }
-
-    public static interface Callback {
-        public void onSessionDestroyed();
-        public void onSessionEvent(String event, Bundle extras);
-        public void onPlaybackStateChanged(Object stateObj);
-        public void onMetadataChanged(Object metadataObj);
-        public void onQueueChanged(List<?> queue);
-        public void onQueueTitleChanged(CharSequence title);
-        public void onExtrasChanged(Bundle extras);
-        public void onAudioInfoChanged(int type, int stream, int control, int max, int current);
-    }
-
-    static class CallbackProxy<T extends Callback> extends MediaController.Callback {
-        protected final T mCallback;
-
-        public CallbackProxy(T callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onSessionDestroyed() {
-            mCallback.onSessionDestroyed();
-        }
-
-        @Override
-        public void onSessionEvent(String event, Bundle extras) {
-            mCallback.onSessionEvent(event, extras);
-        }
-
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            mCallback.onPlaybackStateChanged(state);
-        }
-
-        @Override
-        public void onMetadataChanged(MediaMetadata metadata) {
-            mCallback.onMetadataChanged(metadata);
-        }
-
-        @Override
-        public void onQueueChanged(List<MediaSession.QueueItem> queue) {
-            mCallback.onQueueChanged(queue);
-        }
-
-        @Override
-        public void onQueueTitleChanged(CharSequence title) {
-            mCallback.onQueueTitleChanged(title);
-        }
-
-        @Override
-        public void onExtrasChanged(Bundle extras) {
-            mCallback.onExtrasChanged(extras);
-        }
-
-        @Override
-        public void onAudioInfoChanged(MediaController.PlaybackInfo info){
-            mCallback.onAudioInfoChanged(info.getPlaybackType(),
-                    PlaybackInfo.getLegacyAudioStream(info), info.getVolumeControl(),
-                    info.getMaxVolume(), info.getCurrentVolume());
-        }
-    }
-}
diff --git a/media/api23/android/support/v4/media/MediaBrowserCompatApi23.java b/media/api23/android/support/v4/media/MediaBrowserCompatApi23.java
new file mode 100644
index 0000000..6f94d5d
--- /dev/null
+++ b/media/api23/android/support/v4/media/MediaBrowserCompatApi23.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 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.support.v4.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(23)
+class MediaBrowserCompatApi23 {
+
+    public static Object createItemCallback(ItemCallback callback) {
+        return new ItemCallbackProxy<>(callback);
+    }
+
+    public static void getItem(Object browserObj, String mediaId, Object itemCallbackObj) {
+        ((MediaBrowser) browserObj).getItem(mediaId, ((MediaBrowser.ItemCallback) itemCallbackObj));
+    }
+
+    interface ItemCallback {
+        void onItemLoaded(Parcel itemParcel);
+        void onError(@NonNull String itemId);
+    }
+
+    static class ItemCallbackProxy<T extends ItemCallback> extends MediaBrowser.ItemCallback {
+        protected final T mItemCallback;
+
+        public ItemCallbackProxy(T callback) {
+            mItemCallback = callback;
+        }
+
+        @Override
+        public void onItemLoaded(MediaBrowser.MediaItem item) {
+            if (item == null) {
+                mItemCallback.onItemLoaded(null);
+            } else {
+                Parcel parcel = Parcel.obtain();
+                item.writeToParcel(parcel, 0);
+                mItemCallback.onItemLoaded(parcel);
+            }
+        }
+
+        @Override
+        public void onError(@NonNull String itemId) {
+            mItemCallback.onError(itemId);
+        }
+    }
+}
diff --git a/media/api23/android/support/v4/media/session/MediaControllerCompatApi23.java b/media/api23/android/support/v4/media/session/MediaControllerCompatApi23.java
new file mode 100644
index 0000000..ca424d1
--- /dev/null
+++ b/media/api23/android/support/v4/media/session/MediaControllerCompatApi23.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 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.support.v4.media.session;
+
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(23)
+class MediaControllerCompatApi23 {
+
+    public static class TransportControls extends MediaControllerCompatApi21.TransportControls {
+        public static void playFromUri(Object controlsObj, Uri uri, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).playFromUri(uri, extras);
+        }
+    }
+}
diff --git a/media/api23/androidx/media/MediaBrowserCompatApi23.java b/media/api23/androidx/media/MediaBrowserCompatApi23.java
deleted file mode 100644
index ab05030..0000000
--- a/media/api23/androidx/media/MediaBrowserCompatApi23.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import android.media.browse.MediaBrowser;
-import android.os.Parcel;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(23)
-class MediaBrowserCompatApi23 {
-
-    public static Object createItemCallback(ItemCallback callback) {
-        return new ItemCallbackProxy<>(callback);
-    }
-
-    public static void getItem(Object browserObj, String mediaId, Object itemCallbackObj) {
-        ((MediaBrowser) browserObj).getItem(mediaId, ((MediaBrowser.ItemCallback) itemCallbackObj));
-    }
-
-    interface ItemCallback {
-        void onItemLoaded(Parcel itemParcel);
-        void onError(@NonNull String itemId);
-    }
-
-    static class ItemCallbackProxy<T extends ItemCallback> extends MediaBrowser.ItemCallback {
-        protected final T mItemCallback;
-
-        public ItemCallbackProxy(T callback) {
-            mItemCallback = callback;
-        }
-
-        @Override
-        public void onItemLoaded(MediaBrowser.MediaItem item) {
-            if (item == null) {
-                mItemCallback.onItemLoaded(null);
-            } else {
-                Parcel parcel = Parcel.obtain();
-                item.writeToParcel(parcel, 0);
-                mItemCallback.onItemLoaded(parcel);
-            }
-        }
-
-        @Override
-        public void onError(@NonNull String itemId) {
-            mItemCallback.onError(itemId);
-        }
-    }
-}
diff --git a/media/api23/androidx/media/session/MediaControllerCompatApi23.java b/media/api23/androidx/media/session/MediaControllerCompatApi23.java
deleted file mode 100644
index 036f128..0000000
--- a/media/api23/androidx/media/session/MediaControllerCompatApi23.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media.session;
-
-import android.media.session.MediaController;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(23)
-class MediaControllerCompatApi23 {
-
-    public static class TransportControls extends MediaControllerCompatApi21.TransportControls {
-        public static void playFromUri(Object controlsObj, Uri uri, Bundle extras) {
-            ((MediaController.TransportControls) controlsObj).playFromUri(uri, extras);
-        }
-    }
-}
diff --git a/media/api24/android/support/v4/media/session/MediaControllerCompatApi24.java b/media/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
new file mode 100644
index 0000000..9faeff6
--- /dev/null
+++ b/media/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 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.support.v4.media.session;
+
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(24)
+class MediaControllerCompatApi24 {
+
+    public static class TransportControls extends MediaControllerCompatApi23.TransportControls {
+        public static void prepare(Object controlsObj) {
+            ((MediaController.TransportControls) controlsObj).prepare();
+        }
+
+        public static void prepareFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).prepareFromMediaId(mediaId, extras);
+        }
+
+        public static void prepareFromSearch(Object controlsObj, String query, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).prepareFromSearch(query, extras);
+        }
+
+        public static void prepareFromUri(Object controlsObj, Uri uri, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).prepareFromUri(uri, extras);
+        }
+    }
+}
diff --git a/media/api24/androidx/media/session/MediaControllerCompatApi24.java b/media/api24/androidx/media/session/MediaControllerCompatApi24.java
deleted file mode 100644
index df5d74b..0000000
--- a/media/api24/androidx/media/session/MediaControllerCompatApi24.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media.session;
-
-import android.media.session.MediaController;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(24)
-class MediaControllerCompatApi24 {
-
-    public static class TransportControls extends MediaControllerCompatApi23.TransportControls {
-        public static void prepare(Object controlsObj) {
-            ((MediaController.TransportControls) controlsObj).prepare();
-        }
-
-        public static void prepareFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
-            ((MediaController.TransportControls) controlsObj).prepareFromMediaId(mediaId, extras);
-        }
-
-        public static void prepareFromSearch(Object controlsObj, String query, Bundle extras) {
-            ((MediaController.TransportControls) controlsObj).prepareFromSearch(query, extras);
-        }
-
-        public static void prepareFromUri(Object controlsObj, Uri uri, Bundle extras) {
-            ((MediaController.TransportControls) controlsObj).prepareFromUri(uri, extras);
-        }
-    }
-}
diff --git a/media/api26/android/support/v4/media/MediaBrowserCompatApi26.java b/media/api26/android/support/v4/media/MediaBrowserCompatApi26.java
new file mode 100644
index 0000000..5d556d3
--- /dev/null
+++ b/media/api26/android/support/v4/media/MediaBrowserCompatApi26.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 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.support.v4.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.List;
+
+@RequiresApi(26)
+class MediaBrowserCompatApi26 {
+    static Object createSubscriptionCallback(SubscriptionCallback callback) {
+        return new SubscriptionCallbackProxy<>(callback);
+    }
+
+    public static void subscribe(Object browserObj, String parentId, Bundle options,
+            Object subscriptionCallbackObj) {
+        ((MediaBrowser) browserObj).subscribe(parentId, options,
+                (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
+    }
+
+    public static void unsubscribe(Object browserObj, String parentId,
+            Object subscriptionCallbackObj) {
+        ((MediaBrowser) browserObj).unsubscribe(parentId,
+                (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
+    }
+
+    interface SubscriptionCallback extends MediaBrowserCompatApi21.SubscriptionCallback {
+        void onChildrenLoaded(@NonNull String parentId, List<?> children, @NonNull Bundle options);
+        void onError(@NonNull String parentId, @NonNull  Bundle options);
+    }
+
+    static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
+            extends MediaBrowserCompatApi21.SubscriptionCallbackProxy<T> {
+        SubscriptionCallbackProxy(T callback) {
+            super(callback);
+        }
+
+        @Override
+        public void onChildrenLoaded(@NonNull String parentId,
+                List<MediaBrowser.MediaItem> children, @NonNull Bundle options) {
+            mSubscriptionCallback.onChildrenLoaded(parentId, children, options);
+        }
+
+        @Override
+        public void onError(@NonNull String parentId, @NonNull Bundle options) {
+            mSubscriptionCallback.onError(parentId, options);
+        }
+    }
+}
diff --git a/media/api26/androidx/media/MediaBrowserCompatApi26.java b/media/api26/androidx/media/MediaBrowserCompatApi26.java
deleted file mode 100644
index 7311866..0000000
--- a/media/api26/androidx/media/MediaBrowserCompatApi26.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.util.List;
-
-@RequiresApi(26)
-class MediaBrowserCompatApi26 {
-    static Object createSubscriptionCallback(SubscriptionCallback callback) {
-        return new SubscriptionCallbackProxy<>(callback);
-    }
-
-    public static void subscribe(Object browserObj, String parentId, Bundle options,
-            Object subscriptionCallbackObj) {
-        ((MediaBrowser) browserObj).subscribe(parentId, options,
-                (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
-    }
-
-    public static void unsubscribe(Object browserObj, String parentId,
-            Object subscriptionCallbackObj) {
-        ((MediaBrowser) browserObj).unsubscribe(parentId,
-                (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
-    }
-
-    interface SubscriptionCallback extends MediaBrowserCompatApi21.SubscriptionCallback {
-        void onChildrenLoaded(@NonNull String parentId, List<?> children, @NonNull Bundle options);
-        void onError(@NonNull String parentId, @NonNull  Bundle options);
-    }
-
-    static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
-            extends MediaBrowserCompatApi21.SubscriptionCallbackProxy<T> {
-        SubscriptionCallbackProxy(T callback) {
-            super(callback);
-        }
-
-        @Override
-        public void onChildrenLoaded(@NonNull String parentId,
-                List<MediaBrowser.MediaItem> children, @NonNull Bundle options) {
-            mSubscriptionCallback.onChildrenLoaded(parentId, children, options);
-        }
-
-        @Override
-        public void onError(@NonNull String parentId, @NonNull Bundle options) {
-            mSubscriptionCallback.onError(parentId, options);
-        }
-    }
-}
diff --git a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
new file mode 100644
index 0000000..a0e839b
--- /dev/null
+++ b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
@@ -0,0 +1,2289 @@
+/*
+ * Copyright 2018 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.support.v4.media;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_ADD_SUBSCRIPTION;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_CONNECT;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_DISCONNECT;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_GET_MEDIA_ITEM;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_REGISTER_CALLBACK_MESSENGER;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_REMOVE_SUBSCRIPTION;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_SEARCH;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_SEND_CUSTOM_ACTION;
+import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER;
+import static androidx.media.MediaBrowserProtocol.CLIENT_VERSION_CURRENT;
+import static androidx.media.MediaBrowserProtocol.DATA_CALLBACK_TOKEN;
+import static androidx.media.MediaBrowserProtocol.DATA_CUSTOM_ACTION;
+import static androidx.media.MediaBrowserProtocol.DATA_CUSTOM_ACTION_EXTRAS;
+import static androidx.media.MediaBrowserProtocol.DATA_MEDIA_ITEM_ID;
+import static androidx.media.MediaBrowserProtocol.DATA_MEDIA_ITEM_LIST;
+import static androidx.media.MediaBrowserProtocol.DATA_MEDIA_SESSION_TOKEN;
+import static androidx.media.MediaBrowserProtocol.DATA_OPTIONS;
+import static androidx.media.MediaBrowserProtocol.DATA_PACKAGE_NAME;
+import static androidx.media.MediaBrowserProtocol.DATA_RESULT_RECEIVER;
+import static androidx.media.MediaBrowserProtocol.DATA_ROOT_HINTS;
+import static androidx.media.MediaBrowserProtocol.DATA_SEARCH_EXTRAS;
+import static androidx.media.MediaBrowserProtocol.DATA_SEARCH_QUERY;
+import static androidx.media.MediaBrowserProtocol.EXTRA_CLIENT_VERSION;
+import static androidx.media.MediaBrowserProtocol.EXTRA_MESSENGER_BINDER;
+import static androidx.media.MediaBrowserProtocol.EXTRA_SERVICE_VERSION;
+import static androidx.media.MediaBrowserProtocol.EXTRA_SESSION_BINDER;
+import static androidx.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT;
+import static androidx.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT_FAILED;
+import static androidx.media.MediaBrowserProtocol.SERVICE_MSG_ON_LOAD_CHILDREN;
+import static androidx.media.MediaBrowserProtocol.SERVICE_VERSION_2;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.BadParcelableException;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.support.v4.media.session.IMediaSession;
+import android.support.v4.media.session.MediaControllerCompat.TransportControls;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.os.ResultReceiver;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.collection.ArrayMap;
+import androidx.core.app.BundleCompat;
+import androidx.media.MediaBrowserCompatUtils;
+import androidx.media.MediaBrowserServiceCompat;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Browses media content offered by a {@link MediaBrowserServiceCompat}.
+ * <p>
+ * This object is not thread-safe. All calls should happen on the thread on which the browser
+ * was constructed.
+ * </p><p>
+ * All callback methods will be called from the thread on which the browser was constructed.
+ * </p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about building your media application, read the
+ * <a href="{@docRoot}guide/topics/media-apps/index.html">Media Apps</a> developer guide.</p>
+ * </div>
+ */
+public final class MediaBrowserCompat {
+    static final String TAG = "MediaBrowserCompat";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     * Used as an int extra field to denote the page number to subscribe.
+     * The value of {@code EXTRA_PAGE} should be greater than or equal to 1.
+     *
+     * @see android.service.media.MediaBrowserService.BrowserRoot
+     * @see #EXTRA_PAGE_SIZE
+     */
+    public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+
+    /**
+     * Used as an int extra field to denote the number of media items in a page.
+     * The value of {@code EXTRA_PAGE_SIZE} should be greater than or equal to 1.
+     *
+     * @see android.service.media.MediaBrowserService.BrowserRoot
+     * @see #EXTRA_PAGE
+     */
+    public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+
+    /**
+     * Used as a string extra field to denote the target {@link MediaItem}.
+     *
+     * @see #CUSTOM_ACTION_DOWNLOAD
+     * @see #CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE
+     */
+    public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+
+    /**
+     * Used as a float extra field to denote the current progress during download. The value of this
+     * field must be a float number within [0.0, 1.0].
+     *
+     * @see #CUSTOM_ACTION_DOWNLOAD
+     * @see CustomActionCallback#onProgressUpdate
+     */
+    public static final String EXTRA_DOWNLOAD_PROGRESS =
+            "android.media.browse.extra.DOWNLOAD_PROGRESS";
+
+    /**
+     * Predefined custom action to ask the connected service to download a specific
+     * {@link MediaItem} for offline playback. The id of the media item must be passed in an extra
+     * bundle. The download progress might be delivered to the browser via
+     * {@link CustomActionCallback#onProgressUpdate}.
+     *
+     * @see #EXTRA_MEDIA_ID
+     * @see #EXTRA_DOWNLOAD_PROGRESS
+     * @see #CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE
+     */
+    public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+
+    /**
+     * Predefined custom action to ask the connected service to remove the downloaded file of
+     * {@link MediaItem} by the {@link #CUSTOM_ACTION_DOWNLOAD download} action. The id of the
+     * media item must be passed in an extra bundle.
+     *
+     * @see #EXTRA_MEDIA_ID
+     * @see #CUSTOM_ACTION_DOWNLOAD
+     */
+    public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE =
+            "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+
+    private final MediaBrowserImpl mImpl;
+
+    /**
+     * Creates a media browser for the specified media browse service.
+     *
+     * @param context The context.
+     * @param serviceComponent The component name of the media browse service.
+     * @param callback The connection callback.
+     * @param rootHints An optional bundle of service-specific arguments to send
+     * to the media browse service when connecting and retrieving the root id
+     * for browsing, or null if none. The contents of this bundle may affect
+     * the information returned when browsing.
+     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_RECENT
+     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_OFFLINE
+     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_SUGGESTED
+     */
+    public MediaBrowserCompat(Context context, ComponentName serviceComponent,
+            ConnectionCallback callback, Bundle rootHints) {
+        // To workaround an issue of {@link #unsubscribe(String, SubscriptionCallback)} on API 24
+        // and 25 devices, use the support library version of implementation on those devices.
+        if (Build.VERSION.SDK_INT >= 26) {
+            mImpl = new MediaBrowserImplApi26(context, serviceComponent, callback, rootHints);
+        } else if (Build.VERSION.SDK_INT >= 23) {
+            mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
+        } else if (Build.VERSION.SDK_INT >= 21) {
+            mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
+        } else {
+            mImpl = new MediaBrowserImplBase(context, serviceComponent, callback, rootHints);
+        }
+    }
+
+    /**
+     * Connects to the media browse service.
+     * <p>
+     * The connection callback specified in the constructor will be invoked
+     * when the connection completes or fails.
+     * </p>
+     */
+    public void connect() {
+        mImpl.connect();
+    }
+
+    /**
+     * Disconnects from the media browse service.
+     * After this, no more callbacks will be received.
+     */
+    public void disconnect() {
+        mImpl.disconnect();
+    }
+
+    /**
+     * Returns whether the browser is connected to the service.
+     */
+    public boolean isConnected() {
+        return mImpl.isConnected();
+    }
+
+    /**
+     * Gets the service component that the media browser is connected to.
+     */
+    public @NonNull
+    ComponentName getServiceComponent() {
+        return mImpl.getServiceComponent();
+    }
+
+    /**
+     * Gets the root id.
+     * <p>
+     * Note that the root id may become invalid or change when when the
+     * browser is disconnected.
+     * </p>
+     *
+     * @throws IllegalStateException if not connected.
+     */
+    public @NonNull String getRoot() {
+        return mImpl.getRoot();
+    }
+
+    /**
+     * Gets any extras for the media service.
+     *
+     * @throws IllegalStateException if not connected.
+     */
+    public @Nullable
+    Bundle getExtras() {
+        return mImpl.getExtras();
+    }
+
+    /**
+     * Gets the media session token associated with the media browser.
+     * <p>
+     * Note that the session token may become invalid or change when when the
+     * browser is disconnected.
+     * </p>
+     *
+     * @return The session token for the browser, never null.
+     *
+     * @throws IllegalStateException if not connected.
+     */
+    public @NonNull MediaSessionCompat.Token getSessionToken() {
+        return mImpl.getSessionToken();
+    }
+
+    /**
+     * Queries for information about the media items that are contained within
+     * the specified id and subscribes to receive updates when they change.
+     * <p>
+     * The list of subscriptions is maintained even when not connected and is
+     * restored after the reconnection. It is ok to subscribe while not connected
+     * but the results will not be returned until the connection completes.
+     * </p>
+     * <p>
+     * If the id is already subscribed with a different callback then the new
+     * callback will replace the previous one and the child data will be
+     * reloaded.
+     * </p>
+     *
+     * @param parentId The id of the parent media item whose list of children
+     *            will be subscribed.
+     * @param callback The callback to receive the list of children.
+     */
+    public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
+        // Check arguments.
+        if (TextUtils.isEmpty(parentId)) {
+            throw new IllegalArgumentException("parentId is empty");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback is null");
+        }
+        mImpl.subscribe(parentId, null, callback);
+    }
+
+    /**
+     * Queries with service-specific arguments for information about the media items
+     * that are contained within the specified id and subscribes to receive updates
+     * when they change.
+     * <p>
+     * The list of subscriptions is maintained even when not connected and is
+     * restored after the reconnection. It is ok to subscribe while not connected
+     * but the results will not be returned until the connection completes.
+     * </p>
+     * <p>
+     * If the id is already subscribed with a different callback then the new
+     * callback will replace the previous one and the child data will be
+     * reloaded.
+     * </p>
+     *
+     * @param parentId The id of the parent media item whose list of children
+     *            will be subscribed.
+     * @param options A bundle of service-specific arguments to send to the media
+     *            browse service. The contents of this bundle may affect the
+     *            information returned when browsing.
+     * @param callback The callback to receive the list of children.
+     */
+    public void subscribe(@NonNull String parentId, @NonNull Bundle options,
+            @NonNull SubscriptionCallback callback) {
+        // Check arguments.
+        if (TextUtils.isEmpty(parentId)) {
+            throw new IllegalArgumentException("parentId is empty");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback is null");
+        }
+        if (options == null) {
+            throw new IllegalArgumentException("options are null");
+        }
+        mImpl.subscribe(parentId, options, callback);
+    }
+
+    /**
+     * Unsubscribes for changes to the children of the specified media id.
+     * <p>
+     * The query callback will no longer be invoked for results associated with
+     * this id once this method returns.
+     * </p>
+     *
+     * @param parentId The id of the parent media item whose list of children
+     *            will be unsubscribed.
+     */
+    public void unsubscribe(@NonNull String parentId) {
+        // Check arguments.
+        if (TextUtils.isEmpty(parentId)) {
+            throw new IllegalArgumentException("parentId is empty");
+        }
+        mImpl.unsubscribe(parentId, null);
+    }
+
+    /**
+     * Unsubscribes for changes to the children of the specified media id.
+     * <p>
+     * The query callback will no longer be invoked for results associated with
+     * this id once this method returns.
+     * </p>
+     *
+     * @param parentId The id of the parent media item whose list of children
+     *            will be unsubscribed.
+     * @param callback A callback sent to the media browse service to subscribe.
+     */
+    public void unsubscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
+        // Check arguments.
+        if (TextUtils.isEmpty(parentId)) {
+            throw new IllegalArgumentException("parentId is empty");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback is null");
+        }
+        mImpl.unsubscribe(parentId, callback);
+    }
+
+    /**
+     * Retrieves a specific {@link MediaItem} from the connected service. Not
+     * all services may support this, so falling back to subscribing to the
+     * parent's id should be used when unavailable.
+     *
+     * @param mediaId The id of the item to retrieve.
+     * @param cb The callback to receive the result on.
+     */
+    public void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb) {
+        mImpl.getItem(mediaId, cb);
+    }
+
+    /**
+     * Searches {@link MediaItem media items} from the connected service. Not all services may
+     * support this, and {@link SearchCallback#onError} will be called if not implemented.
+     *
+     * @param query The search query that contains keywords separated by space. Should not be an
+     *            empty string.
+     * @param extras The bundle of service-specific arguments to send to the media browser service.
+     *            The contents of this bundle may affect the search result.
+     * @param callback The callback to receive the search result. Must be non-null.
+     * @throws IllegalStateException if the browser is not connected to the media browser service.
+     */
+    public void search(@NonNull final String query, final Bundle extras,
+            @NonNull SearchCallback callback) {
+        if (TextUtils.isEmpty(query)) {
+            throw new IllegalArgumentException("query cannot be empty");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null");
+        }
+        mImpl.search(query, extras, callback);
+    }
+
+    /**
+     * Sends a custom action to the connected service. If the service doesn't support the given
+     * action, {@link CustomActionCallback#onError} will be called.
+     *
+     * @param action The custom action that will be sent to the connected service. Should not be an
+     *            empty string.
+     * @param extras The bundle of service-specific arguments to send to the media browser service.
+     * @param callback The callback to receive the result of the custom action.
+     * @see #CUSTOM_ACTION_DOWNLOAD
+     * @see #CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE
+     */
+    public void sendCustomAction(@NonNull String action, Bundle extras,
+            @Nullable CustomActionCallback callback) {
+        if (TextUtils.isEmpty(action)) {
+            throw new IllegalArgumentException("action cannot be empty");
+        }
+        mImpl.sendCustomAction(action, extras, callback);
+    }
+
+    /**
+     * A class with information on a single media item for use in browsing/searching media.
+     * MediaItems are application dependent so we cannot guarantee that they contain the
+     * right values.
+     */
+    public static class MediaItem implements Parcelable {
+        private final int mFlags;
+        private final MediaDescriptionCompat mDescription;
+
+        /** @hide */
+        @RestrictTo(LIBRARY_GROUP)
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+        public @interface Flags { }
+
+        /**
+         * Flag: Indicates that the item has children of its own.
+         */
+        public static final int FLAG_BROWSABLE = 1 << 0;
+
+        /**
+         * Flag: Indicates that the item is playable.
+         * <p>
+         * The id of this item may be passed to
+         * {@link TransportControls#playFromMediaId(String, Bundle)}
+         * to start playing it.
+         * </p>
+         */
+        public static final int FLAG_PLAYABLE = 1 << 1;
+
+        /**
+         * Creates an instance from a framework {@link android.media.browse.MediaBrowser.MediaItem}
+         * object.
+         * <p>
+         * This method is only supported on API 21+. On API 20 and below, it returns null.
+         * </p>
+         *
+         * @param itemObj A {@link android.media.browse.MediaBrowser.MediaItem} object.
+         * @return An equivalent {@link MediaItem} object, or null if none.
+         */
+        public static MediaItem fromMediaItem(Object itemObj) {
+            if (itemObj == null || Build.VERSION.SDK_INT < 21) {
+                return null;
+            }
+            int flags = MediaBrowserCompatApi21.MediaItem.getFlags(itemObj);
+            MediaDescriptionCompat description =
+                    MediaDescriptionCompat.fromMediaDescription(
+                            MediaBrowserCompatApi21.MediaItem.getDescription(itemObj));
+            return new MediaItem(description, flags);
+        }
+
+        /**
+         * Creates a list of {@link MediaItem} objects from a framework
+         * {@link android.media.browse.MediaBrowser.MediaItem} object list.
+         * <p>
+         * This method is only supported on API 21+. On API 20 and below, it returns null.
+         * </p>
+         *
+         * @param itemList A list of {@link android.media.browse.MediaBrowser.MediaItem} objects.
+         * @return An equivalent list of {@link MediaItem} objects, or null if none.
+         */
+        public static List<MediaItem> fromMediaItemList(List<?> itemList) {
+            if (itemList == null || Build.VERSION.SDK_INT < 21) {
+                return null;
+            }
+            List<MediaItem> items = new ArrayList<>(itemList.size());
+            for (Object itemObj : itemList) {
+                items.add(fromMediaItem(itemObj));
+            }
+            return items;
+        }
+
+        /**
+         * Create a new MediaItem for use in browsing media.
+         * @param description The description of the media, which must include a
+         *            media id.
+         * @param flags The flags for this item.
+         */
+        public MediaItem(@NonNull MediaDescriptionCompat description, @Flags int flags) {
+            if (description == null) {
+                throw new IllegalArgumentException("description cannot be null");
+            }
+            if (TextUtils.isEmpty(description.getMediaId())) {
+                throw new IllegalArgumentException("description must have a non-empty media id");
+            }
+            mFlags = flags;
+            mDescription = description;
+        }
+
+        /**
+         * Private constructor.
+         */
+        MediaItem(Parcel in) {
+            mFlags = in.readInt();
+            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mFlags);
+            mDescription.writeToParcel(out, flags);
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("MediaItem{");
+            sb.append("mFlags=").append(mFlags);
+            sb.append(", mDescription=").append(mDescription);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        public static final Parcelable.Creator<MediaItem> CREATOR =
+                new Parcelable.Creator<MediaItem>() {
+                    @Override
+                    public MediaItem createFromParcel(Parcel in) {
+                        return new MediaItem(in);
+                    }
+
+                    @Override
+                    public MediaItem[] newArray(int size) {
+                        return new MediaItem[size];
+                    }
+                };
+
+        /**
+         * Gets the flags of the item.
+         */
+        public @Flags int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * Returns whether this item is browsable.
+         * @see #FLAG_BROWSABLE
+         */
+        public boolean isBrowsable() {
+            return (mFlags & FLAG_BROWSABLE) != 0;
+        }
+
+        /**
+         * Returns whether this item is playable.
+         * @see #FLAG_PLAYABLE
+         */
+        public boolean isPlayable() {
+            return (mFlags & FLAG_PLAYABLE) != 0;
+        }
+
+        /**
+         * Returns the description of the media.
+         */
+        public @NonNull MediaDescriptionCompat getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Returns the media id in the {@link MediaDescriptionCompat} for this item.
+         * @see MediaMetadataCompat#METADATA_KEY_MEDIA_ID
+         */
+        public @Nullable String getMediaId() {
+            return mDescription.getMediaId();
+        }
+    }
+
+    /**
+     * Callbacks for connection related events.
+     */
+    public static class ConnectionCallback {
+        final Object mConnectionCallbackObj;
+        ConnectionCallbackInternal mConnectionCallbackInternal;
+
+        public ConnectionCallback() {
+            if (Build.VERSION.SDK_INT >= 21) {
+                mConnectionCallbackObj =
+                        MediaBrowserCompatApi21.createConnectionCallback(new StubApi21());
+            } else {
+                mConnectionCallbackObj = null;
+            }
+        }
+
+        /**
+         * Invoked after {@link MediaBrowserCompat#connect()} when the request has successfully
+         * completed.
+         */
+        public void onConnected() {
+        }
+
+        /**
+         * Invoked when the client is disconnected from the media browser.
+         */
+        public void onConnectionSuspended() {
+        }
+
+        /**
+         * Invoked when the connection to the media browser failed.
+         */
+        public void onConnectionFailed() {
+        }
+
+        void setInternalConnectionCallback(ConnectionCallbackInternal connectionCallbackInternal) {
+            mConnectionCallbackInternal = connectionCallbackInternal;
+        }
+
+        interface ConnectionCallbackInternal {
+            void onConnected();
+            void onConnectionSuspended();
+            void onConnectionFailed();
+        }
+
+        private class StubApi21 implements MediaBrowserCompatApi21.ConnectionCallback {
+            StubApi21() {
+            }
+
+            @Override
+            public void onConnected() {
+                if (mConnectionCallbackInternal != null) {
+                    mConnectionCallbackInternal.onConnected();
+                }
+                ConnectionCallback.this.onConnected();
+            }
+
+            @Override
+            public void onConnectionSuspended() {
+                if (mConnectionCallbackInternal != null) {
+                    mConnectionCallbackInternal.onConnectionSuspended();
+                }
+                ConnectionCallback.this.onConnectionSuspended();
+            }
+
+            @Override
+            public void onConnectionFailed() {
+                if (mConnectionCallbackInternal != null) {
+                    mConnectionCallbackInternal.onConnectionFailed();
+                }
+                ConnectionCallback.this.onConnectionFailed();
+            }
+        }
+    }
+
+    /**
+     * Callbacks for subscription related events.
+     */
+    public static abstract class SubscriptionCallback {
+        private final Object mSubscriptionCallbackObj;
+        private final IBinder mToken;
+        WeakReference<Subscription> mSubscriptionRef;
+
+        public SubscriptionCallback() {
+            mToken = new Binder();
+            if (Build.VERSION.SDK_INT >= 26) {
+                mSubscriptionCallbackObj =
+                        MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26());
+            } else if (Build.VERSION.SDK_INT >= 21) {
+                mSubscriptionCallbackObj =
+                        MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
+            } else {
+                mSubscriptionCallbackObj = null;
+            }
+        }
+
+        /**
+         * Called when the list of children is loaded or updated.
+         *
+         * @param parentId The media id of the parent media item.
+         * @param children The children which were loaded.
+         */
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children) {
+        }
+
+        /**
+         * Called when the list of children is loaded or updated.
+         *
+         * @param parentId The media id of the parent media item.
+         * @param children The children which were loaded.
+         * @param options A bundle of service-specific arguments to send to the media
+         *            browse service. The contents of this bundle may affect the
+         *            information returned when browsing.
+         */
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children,
+                @NonNull Bundle options) {
+        }
+
+        /**
+         * Called when the id doesn't exist or other errors in subscribing.
+         * <p>
+         * If this is called, the subscription remains until {@link MediaBrowserCompat#unsubscribe}
+         * called, because some errors may heal themselves.
+         * </p>
+         *
+         * @param parentId The media id of the parent media item whose children could not be loaded.
+         */
+        public void onError(@NonNull String parentId) {
+        }
+
+        /**
+         * Called when the id doesn't exist or other errors in subscribing.
+         * <p>
+         * If this is called, the subscription remains until {@link MediaBrowserCompat#unsubscribe}
+         * called, because some errors may heal themselves.
+         * </p>
+         *
+         * @param parentId The media id of the parent media item whose children could
+         *            not be loaded.
+         * @param options A bundle of service-specific arguments sent to the media
+         *            browse service.
+         */
+        public void onError(@NonNull String parentId, @NonNull Bundle options) {
+        }
+
+        private void setSubscription(Subscription subscription) {
+            mSubscriptionRef = new WeakReference<>(subscription);
+        }
+
+        private class StubApi21 implements MediaBrowserCompatApi21.SubscriptionCallback {
+            StubApi21() {
+            }
+
+            @Override
+            public void onChildrenLoaded(@NonNull String parentId, List<?> children) {
+                Subscription sub = mSubscriptionRef == null ? null : mSubscriptionRef.get();
+                if (sub == null) {
+                    SubscriptionCallback.this.onChildrenLoaded(
+                            parentId, MediaItem.fromMediaItemList(children));
+                } else {
+                    List<MediaBrowserCompat.MediaItem> itemList =
+                            MediaItem.fromMediaItemList(children);
+                    final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+                    final List<Bundle> optionsList = sub.getOptionsList();
+                    for (int i = 0; i < callbacks.size(); ++i) {
+                        Bundle options = optionsList.get(i);
+                        if (options == null) {
+                            SubscriptionCallback.this.onChildrenLoaded(parentId, itemList);
+                        } else {
+                            SubscriptionCallback.this.onChildrenLoaded(
+                                    parentId, applyOptions(itemList, options), options);
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onError(@NonNull String parentId) {
+                SubscriptionCallback.this.onError(parentId);
+            }
+
+            List<MediaBrowserCompat.MediaItem> applyOptions(List<MediaBrowserCompat.MediaItem> list,
+                    final Bundle options) {
+                if (list == null) {
+                    return null;
+                }
+                int page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
+                int pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+                if (page == -1 && pageSize == -1) {
+                    return list;
+                }
+                int fromIndex = pageSize * page;
+                int toIndex = fromIndex + pageSize;
+                if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
+                    return Collections.EMPTY_LIST;
+                }
+                if (toIndex > list.size()) {
+                    toIndex = list.size();
+                }
+                return list.subList(fromIndex, toIndex);
+            }
+
+        }
+
+        private class StubApi26 extends StubApi21
+                implements MediaBrowserCompatApi26.SubscriptionCallback {
+            StubApi26() {
+            }
+
+            @Override
+            public void onChildrenLoaded(@NonNull String parentId, List<?> children,
+                    @NonNull Bundle options) {
+                SubscriptionCallback.this.onChildrenLoaded(
+                        parentId, MediaItem.fromMediaItemList(children), options);
+            }
+
+            @Override
+            public void onError(@NonNull String parentId, @NonNull Bundle options) {
+                SubscriptionCallback.this.onError(parentId, options);
+            }
+        }
+    }
+
+    /**
+     * Callback for receiving the result of {@link #getItem}.
+     */
+    public static abstract class ItemCallback {
+        final Object mItemCallbackObj;
+
+        public ItemCallback() {
+            if (Build.VERSION.SDK_INT >= 23) {
+                mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
+            } else {
+                mItemCallbackObj = null;
+            }
+        }
+
+        /**
+         * Called when the item has been returned by the browser service.
+         *
+         * @param item The item that was returned or null if it doesn't exist.
+         */
+        public void onItemLoaded(MediaItem item) {
+        }
+
+        /**
+         * Called when the item doesn't exist or there was an error retrieving it.
+         *
+         * @param itemId The media id of the media item which could not be loaded.
+         */
+        public void onError(@NonNull String itemId) {
+        }
+
+        private class StubApi23 implements MediaBrowserCompatApi23.ItemCallback {
+            StubApi23() {
+            }
+
+            @Override
+            public void onItemLoaded(Parcel itemParcel) {
+                if (itemParcel == null) {
+                    ItemCallback.this.onItemLoaded(null);
+                } else {
+                    itemParcel.setDataPosition(0);
+                    MediaItem item =
+                            MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(itemParcel);
+                    itemParcel.recycle();
+                    ItemCallback.this.onItemLoaded(item);
+                }
+            }
+
+            @Override
+            public void onError(@NonNull String itemId) {
+                ItemCallback.this.onError(itemId);
+            }
+        }
+    }
+
+    /**
+     * Callback for receiving the result of {@link #search}.
+     */
+    public abstract static class SearchCallback {
+        /**
+         * Called when the {@link #search} finished successfully.
+         *
+         * @param query The search query sent for the search request to the connected service.
+         * @param extras The bundle of service-specific arguments sent to the connected service.
+         * @param items The list of media items which contains the search result.
+         */
+        public void onSearchResult(@NonNull String query, Bundle extras,
+                @NonNull List<MediaItem> items) {
+        }
+
+        /**
+         * Called when an error happens while {@link #search} or the connected service doesn't
+         * support {@link #search}.
+         *
+         * @param query The search query sent for the search request to the connected service.
+         * @param extras The bundle of service-specific arguments sent to the connected service.
+         */
+        public void onError(@NonNull String query, Bundle extras) {
+        }
+    }
+
+    /**
+     * Callback for receiving the result of {@link #sendCustomAction}.
+     */
+    public abstract static class CustomActionCallback {
+        /**
+         * Called when an interim update was delivered from the connected service while performing
+         * the custom action.
+         *
+         * @param action The custom action sent to the connected service.
+         * @param extras The bundle of service-specific arguments sent to the connected service.
+         * @param data The additional data delivered from the connected service.
+         */
+        public void onProgressUpdate(String action, Bundle extras, Bundle data) {
+        }
+
+        /**
+         * Called when the custom action finished successfully.
+         *
+         * @param action The custom action sent to the connected service.
+         * @param extras The bundle of service-specific arguments sent to the connected service.
+         * @param resultData The additional data delivered from the connected service.
+         */
+        public void onResult(String action, Bundle extras, Bundle resultData) {
+        }
+
+        /**
+         * Called when an error happens while performing the custom action or the connected service
+         * doesn't support the requested custom action.
+         *
+         * @param action The custom action sent to the connected service.
+         * @param extras The bundle of service-specific arguments sent to the connected service.
+         * @param data The additional data delivered from the connected service.
+         */
+        public void onError(String action, Bundle extras, Bundle data) {
+        }
+    }
+
+    interface MediaBrowserImpl {
+        void connect();
+        void disconnect();
+        boolean isConnected();
+        ComponentName getServiceComponent();
+        @NonNull String getRoot();
+        @Nullable Bundle getExtras();
+        @NonNull MediaSessionCompat.Token getSessionToken();
+        void subscribe(@NonNull String parentId, @Nullable Bundle options,
+                @NonNull SubscriptionCallback callback);
+        void unsubscribe(@NonNull String parentId, SubscriptionCallback callback);
+        void getItem(@NonNull String mediaId, @NonNull ItemCallback cb);
+        void search(@NonNull String query, Bundle extras, @NonNull SearchCallback callback);
+        void sendCustomAction(@NonNull String action, Bundle extras,
+                @Nullable CustomActionCallback callback);
+    }
+
+    interface MediaBrowserServiceCallbackImpl {
+        void onServiceConnected(Messenger callback, String root, MediaSessionCompat.Token session,
+                Bundle extra);
+        void onConnectionFailed(Messenger callback);
+        void onLoadChildren(Messenger callback, String parentId, List list, Bundle options);
+    }
+
+    static class MediaBrowserImplBase
+            implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl {
+        static final int CONNECT_STATE_DISCONNECTING = 0;
+        static final int CONNECT_STATE_DISCONNECTED = 1;
+        static final int CONNECT_STATE_CONNECTING = 2;
+        static final int CONNECT_STATE_CONNECTED = 3;
+        static final int CONNECT_STATE_SUSPENDED = 4;
+
+        final Context mContext;
+        final ComponentName mServiceComponent;
+        final ConnectionCallback mCallback;
+        final Bundle mRootHints;
+        final CallbackHandler mHandler = new CallbackHandler(this);
+        private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
+
+        int mState = CONNECT_STATE_DISCONNECTED;
+        MediaServiceConnection mServiceConnection;
+        ServiceBinderWrapper mServiceBinderWrapper;
+        Messenger mCallbacksMessenger;
+        private String mRootId;
+        private MediaSessionCompat.Token mMediaSessionToken;
+        private Bundle mExtras;
+
+        public MediaBrowserImplBase(Context context, ComponentName serviceComponent,
+                ConnectionCallback callback, Bundle rootHints) {
+            if (context == null) {
+                throw new IllegalArgumentException("context must not be null");
+            }
+            if (serviceComponent == null) {
+                throw new IllegalArgumentException("service component must not be null");
+            }
+            if (callback == null) {
+                throw new IllegalArgumentException("connection callback must not be null");
+            }
+            mContext = context;
+            mServiceComponent = serviceComponent;
+            mCallback = callback;
+            mRootHints = rootHints == null ? null : new Bundle(rootHints);
+        }
+
+        @Override
+        public void connect() {
+            if (mState != CONNECT_STATE_DISCONNECTING && mState != CONNECT_STATE_DISCONNECTED) {
+                throw new IllegalStateException("connect() called while neigther disconnecting nor "
+                        + "disconnected (state=" + getStateLabel(mState) + ")");
+            }
+
+            mState = CONNECT_STATE_CONNECTING;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    // mState could be changed by the Runnable of disconnect()
+                    if (mState == CONNECT_STATE_DISCONNECTING) {
+                        return;
+                    }
+                    mState = CONNECT_STATE_CONNECTING;
+                    // TODO: remove this extra check.
+                    if (DEBUG) {
+                        if (mServiceConnection != null) {
+                            throw new RuntimeException("mServiceConnection should be null. Instead "
+                                    + "it is " + mServiceConnection);
+                        }
+                    }
+                    if (mServiceBinderWrapper != null) {
+                        throw new RuntimeException("mServiceBinderWrapper should be null. Instead "
+                                + "it is " + mServiceBinderWrapper);
+                    }
+                    if (mCallbacksMessenger != null) {
+                        throw new RuntimeException("mCallbacksMessenger should be null. Instead "
+                                + "it is " + mCallbacksMessenger);
+                    }
+
+                    final Intent intent = new Intent(MediaBrowserServiceCompat.SERVICE_INTERFACE);
+                    intent.setComponent(mServiceComponent);
+
+                    mServiceConnection = new MediaServiceConnection();
+                    boolean bound = false;
+                    try {
+                        bound = mContext.bindService(intent, mServiceConnection,
+                                Context.BIND_AUTO_CREATE);
+                    } catch (Exception ex) {
+                        Log.e(TAG, "Failed binding to service " + mServiceComponent);
+                    }
+
+                    if (!bound) {
+                        // Tell them that it didn't work.
+                        forceCloseConnection();
+                        mCallback.onConnectionFailed();
+                    }
+
+                    if (DEBUG) {
+                        Log.d(TAG, "connect...");
+                        dump();
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void disconnect() {
+            // It's ok to call this any state, because allowing this lets apps not have
+            // to check isConnected() unnecessarily. They won't appreciate the extra
+            // assertions for this. We do everything we can here to go back to a sane state.
+            mState = CONNECT_STATE_DISCONNECTING;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    // connect() could be called before this. Then we will disconnect and reconnect.
+                    if (mCallbacksMessenger != null) {
+                        try {
+                            mServiceBinderWrapper.disconnect(mCallbacksMessenger);
+                        } catch (RemoteException ex) {
+                            // We are disconnecting anyway. Log, just for posterity but it's not
+                            // a big problem.
+                            Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
+                        }
+                    }
+                    int state = mState;
+                    forceCloseConnection();
+                    // If the state was not CONNECT_STATE_DISCONNECTING, keep the state so that
+                    // the operation came after disconnect() can be handled properly.
+                    if (state != CONNECT_STATE_DISCONNECTING) {
+                        mState = state;
+                    }
+                    if (DEBUG) {
+                        Log.d(TAG, "disconnect...");
+                        dump();
+                    }
+                }
+            });
+        }
+
+        /**
+         * Null out the variables and unbind from the service. This doesn't include
+         * calling disconnect on the service, because we only try to do that in the
+         * clean shutdown cases.
+         * <p>
+         * Everywhere that calls this EXCEPT for disconnect() should follow it with
+         * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback
+         * for a clean shutdown, but everywhere else is a dirty shutdown and should
+         * notify the app.
+         */
+        void forceCloseConnection() {
+            if (mServiceConnection != null) {
+                mContext.unbindService(mServiceConnection);
+            }
+            mState = CONNECT_STATE_DISCONNECTED;
+            mServiceConnection = null;
+            mServiceBinderWrapper = null;
+            mCallbacksMessenger = null;
+            mHandler.setCallbacksMessenger(null);
+            mRootId = null;
+            mMediaSessionToken = null;
+        }
+
+        @Override
+        public boolean isConnected() {
+            return mState == CONNECT_STATE_CONNECTED;
+        }
+
+        @Override
+        public @NonNull ComponentName getServiceComponent() {
+            if (!isConnected()) {
+                throw new IllegalStateException("getServiceComponent() called while not connected" +
+                        " (state=" + mState + ")");
+            }
+            return mServiceComponent;
+        }
+
+        @Override
+        public @NonNull String getRoot() {
+            if (!isConnected()) {
+                throw new IllegalStateException("getRoot() called while not connected"
+                        + "(state=" + getStateLabel(mState) + ")");
+            }
+            return mRootId;
+        }
+
+        @Override
+        public @Nullable Bundle getExtras() {
+            if (!isConnected()) {
+                throw new IllegalStateException("getExtras() called while not connected (state="
+                        + getStateLabel(mState) + ")");
+            }
+            return mExtras;
+        }
+
+        @Override
+        public @NonNull MediaSessionCompat.Token getSessionToken() {
+            if (!isConnected()) {
+                throw new IllegalStateException("getSessionToken() called while not connected"
+                        + "(state=" + mState + ")");
+            }
+            return mMediaSessionToken;
+        }
+
+        @Override
+        public void subscribe(@NonNull String parentId, Bundle options,
+                @NonNull SubscriptionCallback callback) {
+            // Update or create the subscription.
+            Subscription sub = mSubscriptions.get(parentId);
+            if (sub == null) {
+                sub = new Subscription();
+                mSubscriptions.put(parentId, sub);
+            }
+            Bundle copiedOptions = options == null ? null : new Bundle(options);
+            sub.putCallback(mContext, copiedOptions, callback);
+
+            // If we are connected, tell the service that we are watching. If we aren't
+            // connected, the service will be told when we connect.
+            if (isConnected()) {
+                try {
+                    mServiceBinderWrapper.addSubscription(parentId, callback.mToken, copiedOptions,
+                            mCallbacksMessenger);
+                } catch (RemoteException e) {
+                    // Process is crashing. We will disconnect, and upon reconnect we will
+                    // automatically reregister. So nothing to do here.
+                    Log.d(TAG, "addSubscription failed with RemoteException parentId=" + parentId);
+                }
+            }
+        }
+
+        @Override
+        public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
+            Subscription sub = mSubscriptions.get(parentId);
+            if (sub == null) {
+                return;
+            }
+
+            // Tell the service if necessary.
+            try {
+                if (callback == null) {
+                    if (isConnected()) {
+                        mServiceBinderWrapper.removeSubscription(parentId, null,
+                                mCallbacksMessenger);
+                    }
+                } else {
+                    final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+                    final List<Bundle> optionsList = sub.getOptionsList();
+                    for (int i = callbacks.size() - 1; i >= 0; --i) {
+                        if (callbacks.get(i) == callback) {
+                            if (isConnected()) {
+                                mServiceBinderWrapper.removeSubscription(
+                                        parentId, callback.mToken, mCallbacksMessenger);
+                            }
+                            callbacks.remove(i);
+                            optionsList.remove(i);
+                        }
+                    }
+                }
+            } catch (RemoteException ex) {
+                // Process is crashing. We will disconnect, and upon reconnect we will
+                // automatically reregister. So nothing to do here.
+                Log.d(TAG, "removeSubscription failed with RemoteException parentId=" + parentId);
+            }
+
+            if (sub.isEmpty() || callback == null) {
+                mSubscriptions.remove(parentId);
+            }
+        }
+
+        @Override
+        public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
+            if (TextUtils.isEmpty(mediaId)) {
+                throw new IllegalArgumentException("mediaId is empty");
+            }
+            if (cb == null) {
+                throw new IllegalArgumentException("cb is null");
+            }
+            if (!isConnected()) {
+                Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        cb.onError(mediaId);
+                    }
+                });
+                return;
+            }
+            ResultReceiver receiver = new ItemReceiver(mediaId, cb, mHandler);
+            try {
+                mServiceBinderWrapper.getMediaItem(mediaId, receiver, mCallbacksMessenger);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Remote error getting media item: " + mediaId);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        cb.onError(mediaId);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void search(@NonNull final String query, final Bundle extras,
+                @NonNull final SearchCallback callback) {
+            if (!isConnected()) {
+                throw new IllegalStateException("search() called while not connected"
+                        + " (state=" + getStateLabel(mState) + ")");
+            }
+
+            ResultReceiver receiver = new SearchResultReceiver(query, extras, callback, mHandler);
+            try {
+                mServiceBinderWrapper.search(query, extras, receiver, mCallbacksMessenger);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Remote error searching items with query: " + query, e);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onError(query, extras);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void sendCustomAction(@NonNull final String action, final Bundle extras,
+                @Nullable final CustomActionCallback callback) {
+            if (!isConnected()) {
+                throw new IllegalStateException("Cannot send a custom action (" + action + ") with "
+                        + "extras " + extras + " because the browser is not connected to the "
+                        + "service.");
+            }
+
+            ResultReceiver receiver = new CustomActionResultReceiver(action, extras, callback,
+                    mHandler);
+            try {
+                mServiceBinderWrapper.sendCustomAction(action, extras, receiver,
+                        mCallbacksMessenger);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Remote error sending a custom action: action=" + action + ", extras="
+                        + extras, e);
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onError(action, extras, null);
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
+        public void onServiceConnected(final Messenger callback, final String root,
+                final MediaSessionCompat.Token session, final Bundle extra) {
+            // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
+            if (!isCurrent(callback, "onConnect")) {
+                return;
+            }
+            // Don't allow them to call us twice.
+            if (mState != CONNECT_STATE_CONNECTING) {
+                Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
+                        + "... ignoring");
+                return;
+            }
+            mRootId = root;
+            mMediaSessionToken = session;
+            mExtras = extra;
+            mState = CONNECT_STATE_CONNECTED;
+
+            if (DEBUG) {
+                Log.d(TAG, "ServiceCallbacks.onConnect...");
+                dump();
+            }
+            mCallback.onConnected();
+
+            // we may receive some subscriptions before we are connected, so re-subscribe
+            // everything now
+            try {
+                for (Map.Entry<String, Subscription> subscriptionEntry
+                        : mSubscriptions.entrySet()) {
+                    String id = subscriptionEntry.getKey();
+                    Subscription sub = subscriptionEntry.getValue();
+                    List<SubscriptionCallback> callbackList = sub.getCallbacks();
+                    List<Bundle> optionsList = sub.getOptionsList();
+                    for (int i = 0; i < callbackList.size(); ++i) {
+                        mServiceBinderWrapper.addSubscription(id, callbackList.get(i).mToken,
+                                optionsList.get(i), mCallbacksMessenger);
+                    }
+                }
+            } catch (RemoteException ex) {
+                // Process is crashing. We will disconnect, and upon reconnect we will
+                // automatically reregister. So nothing to do here.
+                Log.d(TAG, "addSubscription failed with RemoteException.");
+            }
+        }
+
+        @Override
+        public void onConnectionFailed(final Messenger callback) {
+            Log.e(TAG, "onConnectFailed for " + mServiceComponent);
+
+            // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
+            if (!isCurrent(callback, "onConnectFailed")) {
+                return;
+            }
+            // Don't allow them to call us twice.
+            if (mState != CONNECT_STATE_CONNECTING) {
+                Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
+                        + "... ignoring");
+                return;
+            }
+
+            // Clean up
+            forceCloseConnection();
+
+            // Tell the app.
+            mCallback.onConnectionFailed();
+        }
+
+        @Override
+        public void onLoadChildren(final Messenger callback, final String parentId,
+                final List list, final Bundle options) {
+            // Check that there hasn't been a disconnect or a different ServiceConnection.
+            if (!isCurrent(callback, "onLoadChildren")) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
+            }
+
+            // Check that the subscription is still subscribed.
+            final Subscription subscription = mSubscriptions.get(parentId);
+            if (subscription == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
+                }
+                return;
+            }
+
+            // Tell the app.
+            SubscriptionCallback subscriptionCallback = subscription.getCallback(mContext, options);
+            if (subscriptionCallback != null) {
+                if (options == null) {
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list);
+                    }
+                } else {
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId, options);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list, options);
+                    }
+                }
+            }
+        }
+
+        /**
+         * For debugging.
+         */
+        private static String getStateLabel(int state) {
+            switch (state) {
+                case CONNECT_STATE_DISCONNECTING:
+                    return "CONNECT_STATE_DISCONNECTING";
+                case CONNECT_STATE_DISCONNECTED:
+                    return "CONNECT_STATE_DISCONNECTED";
+                case CONNECT_STATE_CONNECTING:
+                    return "CONNECT_STATE_CONNECTING";
+                case CONNECT_STATE_CONNECTED:
+                    return "CONNECT_STATE_CONNECTED";
+                case CONNECT_STATE_SUSPENDED:
+                    return "CONNECT_STATE_SUSPENDED";
+                default:
+                    return "UNKNOWN/" + state;
+            }
+        }
+
+        /**
+         * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
+         */
+        @SuppressWarnings("ReferenceEquality")
+        private boolean isCurrent(Messenger callback, String funcName) {
+            if (mCallbacksMessenger != callback || mState == CONNECT_STATE_DISCONNECTING
+                    || mState == CONNECT_STATE_DISCONNECTED) {
+                if (mState != CONNECT_STATE_DISCONNECTING && mState != CONNECT_STATE_DISCONNECTED) {
+                    Log.i(TAG, funcName + " for " + mServiceComponent + " with mCallbacksMessenger="
+                            + mCallbacksMessenger + " this=" + this);
+                }
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Log internal state.
+         */
+        void dump() {
+            Log.d(TAG, "MediaBrowserCompat...");
+            Log.d(TAG, "  mServiceComponent=" + mServiceComponent);
+            Log.d(TAG, "  mCallback=" + mCallback);
+            Log.d(TAG, "  mRootHints=" + mRootHints);
+            Log.d(TAG, "  mState=" + getStateLabel(mState));
+            Log.d(TAG, "  mServiceConnection=" + mServiceConnection);
+            Log.d(TAG, "  mServiceBinderWrapper=" + mServiceBinderWrapper);
+            Log.d(TAG, "  mCallbacksMessenger=" + mCallbacksMessenger);
+            Log.d(TAG, "  mRootId=" + mRootId);
+            Log.d(TAG, "  mMediaSessionToken=" + mMediaSessionToken);
+        }
+
+        /**
+         * ServiceConnection to the other app.
+         */
+        private class MediaServiceConnection implements ServiceConnection {
+            MediaServiceConnection() {
+            }
+
+            @Override
+            public void onServiceConnected(final ComponentName name, final IBinder binder) {
+                postOrRun(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (DEBUG) {
+                            Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
+                                    + " binder=" + binder);
+                            dump();
+                        }
+
+                        // Make sure we are still the current connection, and that they haven't
+                        // called disconnect().
+                        if (!isCurrent("onServiceConnected")) {
+                            return;
+                        }
+
+                        // Save their binder
+                        mServiceBinderWrapper = new ServiceBinderWrapper(binder, mRootHints);
+
+                        // We make a new mServiceCallbacks each time we connect so that we can drop
+                        // responses from previous connections.
+                        mCallbacksMessenger = new Messenger(mHandler);
+                        mHandler.setCallbacksMessenger(mCallbacksMessenger);
+
+                        mState = CONNECT_STATE_CONNECTING;
+
+                        // Call connect, which is async. When we get a response from that we will
+                        // say that we're connected.
+                        try {
+                            if (DEBUG) {
+                                Log.d(TAG, "ServiceCallbacks.onConnect...");
+                                dump();
+                            }
+                            mServiceBinderWrapper.connect(mContext, mCallbacksMessenger);
+                        } catch (RemoteException ex) {
+                            // Connect failed, which isn't good. But the auto-reconnect on the
+                            // service will take over and we will come back. We will also get the
+                            // onServiceDisconnected, which has all the cleanup code. So let that
+                            // do it.
+                            Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
+                            if (DEBUG) {
+                                Log.d(TAG, "ServiceCallbacks.onConnect...");
+                                dump();
+                            }
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void onServiceDisconnected(final ComponentName name) {
+                postOrRun(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (DEBUG) {
+                            Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
+                                    + " this=" + this + " mServiceConnection=" +
+                                    mServiceConnection);
+                            dump();
+                        }
+
+                        // Make sure we are still the current connection, and that they haven't
+                        // called disconnect().
+                        if (!isCurrent("onServiceDisconnected")) {
+                            return;
+                        }
+
+                        // Clear out what we set in onServiceConnected
+                        mServiceBinderWrapper = null;
+                        mCallbacksMessenger = null;
+                        mHandler.setCallbacksMessenger(null);
+
+                        // And tell the app that it's suspended.
+                        mState = CONNECT_STATE_SUSPENDED;
+                        mCallback.onConnectionSuspended();
+                    }
+                });
+            }
+
+            private void postOrRun(Runnable r) {
+                if (Thread.currentThread() == mHandler.getLooper().getThread()) {
+                    r.run();
+                } else {
+                    mHandler.post(r);
+                }
+            }
+
+            /**
+             * Return true if this is the current ServiceConnection. Also logs if it's not.
+             */
+            boolean isCurrent(String funcName) {
+                if (mServiceConnection != this || mState == CONNECT_STATE_DISCONNECTING
+                        || mState == CONNECT_STATE_DISCONNECTED) {
+                    if (mState != CONNECT_STATE_DISCONNECTING
+                            && mState != CONNECT_STATE_DISCONNECTED) {
+                        // Check mState, because otherwise this log is noisy.
+                        Log.i(TAG, funcName + " for " + mServiceComponent +
+                                " with mServiceConnection=" + mServiceConnection + " this=" + this);
+                    }
+                    return false;
+                }
+                return true;
+            }
+        }
+    }
+
+    @RequiresApi(21)
+    static class MediaBrowserImplApi21 implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl,
+            ConnectionCallback.ConnectionCallbackInternal {
+        final Context mContext;
+        protected final Object mBrowserObj;
+        protected final Bundle mRootHints;
+        protected final CallbackHandler mHandler = new CallbackHandler(this);
+        private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
+
+        protected int mServiceVersion;
+        protected ServiceBinderWrapper mServiceBinderWrapper;
+        protected Messenger mCallbacksMessenger;
+        private MediaSessionCompat.Token mMediaSessionToken;
+
+        MediaBrowserImplApi21(Context context, ComponentName serviceComponent,
+                ConnectionCallback callback, Bundle rootHints) {
+            mContext = context;
+            if (rootHints == null) {
+                rootHints = new Bundle();
+            }
+            rootHints.putInt(EXTRA_CLIENT_VERSION, CLIENT_VERSION_CURRENT);
+            mRootHints = new Bundle(rootHints);
+            callback.setInternalConnectionCallback(this);
+            mBrowserObj = MediaBrowserCompatApi21.createBrowser(context, serviceComponent,
+                    callback.mConnectionCallbackObj, mRootHints);
+        }
+
+        @Override
+        public void connect() {
+            MediaBrowserCompatApi21.connect(mBrowserObj);
+        }
+
+        @Override
+        public void disconnect() {
+            if (mServiceBinderWrapper != null && mCallbacksMessenger != null) {
+                try {
+                    mServiceBinderWrapper.unregisterCallbackMessenger(mCallbacksMessenger);
+                } catch (RemoteException e) {
+                    Log.i(TAG, "Remote error unregistering client messenger." );
+                }
+            }
+            MediaBrowserCompatApi21.disconnect(mBrowserObj);
+        }
+
+        @Override
+        public boolean isConnected() {
+            return MediaBrowserCompatApi21.isConnected(mBrowserObj);
+        }
+
+        @Override
+        public ComponentName getServiceComponent() {
+            return MediaBrowserCompatApi21.getServiceComponent(mBrowserObj);
+        }
+
+        @NonNull
+        @Override
+        public String getRoot() {
+            return MediaBrowserCompatApi21.getRoot(mBrowserObj);
+        }
+
+        @Nullable
+        @Override
+        public Bundle getExtras() {
+            return MediaBrowserCompatApi21.getExtras(mBrowserObj);
+        }
+
+        @NonNull
+        @Override
+        public MediaSessionCompat.Token getSessionToken() {
+            if (mMediaSessionToken == null) {
+                mMediaSessionToken = MediaSessionCompat.Token.fromToken(
+                        MediaBrowserCompatApi21.getSessionToken(mBrowserObj));
+            }
+            return mMediaSessionToken;
+        }
+
+        @Override
+        public void subscribe(@NonNull final String parentId, final Bundle options,
+                @NonNull final SubscriptionCallback callback) {
+            // Update or create the subscription.
+            Subscription sub = mSubscriptions.get(parentId);
+            if (sub == null) {
+                sub = new Subscription();
+                mSubscriptions.put(parentId, sub);
+            }
+            callback.setSubscription(sub);
+            Bundle copiedOptions = options == null ? null : new Bundle(options);
+            sub.putCallback(mContext, copiedOptions, callback);
+
+            if (mServiceBinderWrapper == null) {
+                // TODO: When MediaBrowser is connected to framework's MediaBrowserService,
+                // subscribe with options won't work properly.
+                MediaBrowserCompatApi21.subscribe(
+                        mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+            } else {
+                try {
+                    mServiceBinderWrapper.addSubscription(
+                            parentId, callback.mToken, copiedOptions, mCallbacksMessenger);
+                } catch (RemoteException e) {
+                    // Process is crashing. We will disconnect, and upon reconnect we will
+                    // automatically reregister. So nothing to do here.
+                    Log.i(TAG, "Remote error subscribing media item: " + parentId);
+                }
+            }
+        }
+
+        @Override
+        public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
+            Subscription sub = mSubscriptions.get(parentId);
+            if (sub == null) {
+                return;
+            }
+
+            if (mServiceBinderWrapper == null) {
+                if (callback == null) {
+                    MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+                } else {
+                    final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+                    final List<Bundle> optionsList = sub.getOptionsList();
+                    for (int i = callbacks.size() - 1; i >= 0; --i) {
+                        if (callbacks.get(i) == callback) {
+                            callbacks.remove(i);
+                            optionsList.remove(i);
+                        }
+                    }
+                    if (callbacks.size() == 0) {
+                        MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+                    }
+                }
+            } else {
+                // Tell the service if necessary.
+                try {
+                    if (callback == null) {
+                        mServiceBinderWrapper.removeSubscription(parentId, null,
+                                mCallbacksMessenger);
+                    } else {
+                        final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+                        final List<Bundle> optionsList = sub.getOptionsList();
+                        for (int i = callbacks.size() - 1; i >= 0; --i) {
+                            if (callbacks.get(i) == callback) {
+                                mServiceBinderWrapper.removeSubscription(
+                                        parentId, callback.mToken, mCallbacksMessenger);
+                                callbacks.remove(i);
+                                optionsList.remove(i);
+                            }
+                        }
+                    }
+                } catch (RemoteException ex) {
+                    // Process is crashing. We will disconnect, and upon reconnect we will
+                    // automatically reregister. So nothing to do here.
+                    Log.d(TAG, "removeSubscription failed with RemoteException parentId="
+                            + parentId);
+                }
+            }
+
+            if (sub.isEmpty() || callback == null) {
+                mSubscriptions.remove(parentId);
+            }
+        }
+
+        @Override
+        public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
+            if (TextUtils.isEmpty(mediaId)) {
+                throw new IllegalArgumentException("mediaId is empty");
+            }
+            if (cb == null) {
+                throw new IllegalArgumentException("cb is null");
+            }
+            if (!MediaBrowserCompatApi21.isConnected(mBrowserObj)) {
+                Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        cb.onError(mediaId);
+                    }
+                });
+                return;
+            }
+            if (mServiceBinderWrapper == null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Default framework implementation.
+                        cb.onError(mediaId);
+                    }
+                });
+                return;
+            }
+            ResultReceiver receiver = new ItemReceiver(mediaId, cb, mHandler);
+            try {
+                mServiceBinderWrapper.getMediaItem(mediaId, receiver, mCallbacksMessenger);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Remote error getting media item: " + mediaId);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        cb.onError(mediaId);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void search(@NonNull final String query, final Bundle extras,
+                @NonNull final SearchCallback callback) {
+            if (!isConnected()) {
+                throw new IllegalStateException("search() called while not connected");
+            }
+            if (mServiceBinderWrapper == null) {
+                Log.i(TAG, "The connected service doesn't support search.");
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Default framework implementation.
+                        callback.onError(query, extras);
+                    }
+                });
+                return;
+            }
+
+            ResultReceiver receiver = new SearchResultReceiver(query, extras, callback, mHandler);
+            try {
+                mServiceBinderWrapper.search(query, extras, receiver, mCallbacksMessenger);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Remote error searching items with query: " + query, e);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onError(query, extras);
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void sendCustomAction(@NonNull final String action, final Bundle extras,
+                @Nullable final CustomActionCallback callback) {
+            if (!isConnected()) {
+                throw new IllegalStateException("Cannot send a custom action (" + action + ") with "
+                        + "extras " + extras + " because the browser is not connected to the "
+                        + "service.");
+            }
+            if (mServiceBinderWrapper == null) {
+                Log.i(TAG, "The connected service doesn't support sendCustomAction.");
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onError(action, extras, null);
+                        }
+                    });
+                }
+            }
+
+            ResultReceiver receiver = new CustomActionResultReceiver(action, extras, callback,
+                    mHandler);
+            try {
+                mServiceBinderWrapper.sendCustomAction(action, extras, receiver,
+                        mCallbacksMessenger);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Remote error sending a custom action: action=" + action + ", extras="
+                        + extras, e);
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onError(action, extras, null);
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
+        public void onConnected() {
+            Bundle extras = MediaBrowserCompatApi21.getExtras(mBrowserObj);
+            if (extras == null) {
+                return;
+            }
+            mServiceVersion = extras.getInt(EXTRA_SERVICE_VERSION, 0);
+            IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
+            if (serviceBinder != null) {
+                mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints);
+                mCallbacksMessenger = new Messenger(mHandler);
+                mHandler.setCallbacksMessenger(mCallbacksMessenger);
+                try {
+                    mServiceBinderWrapper.registerCallbackMessenger(mCallbacksMessenger);
+                } catch (RemoteException e) {
+                    Log.i(TAG, "Remote error registering client messenger." );
+                }
+            }
+            IMediaSession sessionToken = IMediaSession.Stub.asInterface(
+                    BundleCompat.getBinder(extras, EXTRA_SESSION_BINDER));
+            if (sessionToken != null) {
+                mMediaSessionToken = MediaSessionCompat.Token.fromToken(
+                        MediaBrowserCompatApi21.getSessionToken(mBrowserObj), sessionToken);
+            }
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            mServiceBinderWrapper = null;
+            mCallbacksMessenger = null;
+            mMediaSessionToken = null;
+            mHandler.setCallbacksMessenger(null);
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            // Do noting
+        }
+
+        @Override
+        public void onServiceConnected(final Messenger callback, final String root,
+                final MediaSessionCompat.Token session, final Bundle extra) {
+            // This method will not be called.
+        }
+
+        @Override
+        public void onConnectionFailed(Messenger callback) {
+            // This method will not be called.
+        }
+
+        @Override
+        @SuppressWarnings("ReferenceEquality")
+        public void onLoadChildren(Messenger callback, String parentId, List list, Bundle options) {
+            if (mCallbacksMessenger != callback) {
+                return;
+            }
+
+            // Check that the subscription is still subscribed.
+            Subscription subscription = mSubscriptions.get(parentId);
+            if (subscription == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
+                }
+                return;
+            }
+
+            // Tell the app.
+            SubscriptionCallback subscriptionCallback = subscription.getCallback(mContext, options);
+            if (subscriptionCallback != null) {
+                if (options == null) {
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list);
+                    }
+                } else {
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId, options);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list, options);
+                    }
+                }
+            }
+        }
+    }
+
+    @RequiresApi(23)
+    static class MediaBrowserImplApi23 extends MediaBrowserImplApi21 {
+        MediaBrowserImplApi23(Context context, ComponentName serviceComponent,
+                ConnectionCallback callback, Bundle rootHints) {
+            super(context, serviceComponent, callback, rootHints);
+        }
+
+        @Override
+        public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
+            if (mServiceBinderWrapper == null) {
+                MediaBrowserCompatApi23.getItem(mBrowserObj, mediaId, cb.mItemCallbackObj);
+            } else {
+                super.getItem(mediaId, cb);
+            }
+        }
+    }
+
+    @RequiresApi(26)
+    static class MediaBrowserImplApi26 extends MediaBrowserImplApi23 {
+        MediaBrowserImplApi26(Context context, ComponentName serviceComponent,
+                ConnectionCallback callback, Bundle rootHints) {
+            super(context, serviceComponent, callback, rootHints);
+        }
+
+        @Override
+        public void subscribe(@NonNull String parentId, @Nullable Bundle options,
+                @NonNull SubscriptionCallback callback) {
+            // From service v2, we use compat code when subscribing.
+            // This is to prevent ClassNotFoundException when options has Parcelable in it.
+            if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
+                if (options == null) {
+                    MediaBrowserCompatApi21.subscribe(
+                            mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+                } else {
+                    MediaBrowserCompatApi26.subscribe(
+                            mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+                }
+            } else {
+                super.subscribe(parentId, options, callback);
+            }
+        }
+
+        @Override
+        public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
+            // From service v2, we use compat code when subscribing.
+            // This is to prevent ClassNotFoundException when options has Parcelable in it.
+            if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
+                if (callback == null) {
+                    MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+                } else {
+                    MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
+                            callback.mSubscriptionCallbackObj);
+                }
+            } else {
+                super.unsubscribe(parentId, callback);
+            }
+        }
+    }
+
+    private static class Subscription {
+        private final List<SubscriptionCallback> mCallbacks;
+        private final List<Bundle> mOptionsList;
+
+        public Subscription() {
+            mCallbacks = new ArrayList<>();
+            mOptionsList = new ArrayList<>();
+        }
+
+        public boolean isEmpty() {
+            return mCallbacks.isEmpty();
+        }
+
+        public List<Bundle> getOptionsList() {
+            return mOptionsList;
+        }
+
+        public List<SubscriptionCallback> getCallbacks() {
+            return mCallbacks;
+        }
+
+        public SubscriptionCallback getCallback(Context context, Bundle options) {
+            if (options != null) {
+                options.setClassLoader(context.getClassLoader());
+            }
+            for (int i = 0; i < mOptionsList.size(); ++i) {
+                if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
+                    return mCallbacks.get(i);
+                }
+            }
+            return null;
+        }
+
+        public void putCallback(Context context, Bundle options, SubscriptionCallback callback) {
+            if (options != null) {
+                options.setClassLoader(context.getClassLoader());
+            }
+            for (int i = 0; i < mOptionsList.size(); ++i) {
+                if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
+                    mCallbacks.set(i, callback);
+                    return;
+                }
+            }
+            mCallbacks.add(callback);
+            mOptionsList.add(options);
+        }
+    }
+
+    private static class CallbackHandler extends Handler {
+        private final WeakReference<MediaBrowserServiceCallbackImpl> mCallbackImplRef;
+        private WeakReference<Messenger> mCallbacksMessengerRef;
+
+        CallbackHandler(MediaBrowserServiceCallbackImpl callbackImpl) {
+            super();
+            mCallbackImplRef = new WeakReference<>(callbackImpl);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mCallbacksMessengerRef == null || mCallbacksMessengerRef.get() == null ||
+                    mCallbackImplRef.get() == null) {
+                return;
+            }
+            Bundle data = msg.getData();
+            data.setClassLoader(MediaSessionCompat.class.getClassLoader());
+            MediaBrowserServiceCallbackImpl serviceCallback = mCallbackImplRef.get();
+            Messenger callbacksMessenger = mCallbacksMessengerRef.get();
+            try {
+                switch (msg.what) {
+                    case SERVICE_MSG_ON_CONNECT:
+                        serviceCallback.onServiceConnected(callbacksMessenger,
+                                data.getString(DATA_MEDIA_ITEM_ID),
+                                (MediaSessionCompat.Token) data.getParcelable(
+                                        DATA_MEDIA_SESSION_TOKEN),
+                                data.getBundle(DATA_ROOT_HINTS));
+                        break;
+                    case SERVICE_MSG_ON_CONNECT_FAILED:
+                        serviceCallback.onConnectionFailed(callbacksMessenger);
+                        break;
+                    case SERVICE_MSG_ON_LOAD_CHILDREN:
+                        serviceCallback.onLoadChildren(callbacksMessenger,
+                                data.getString(DATA_MEDIA_ITEM_ID),
+                                data.getParcelableArrayList(DATA_MEDIA_ITEM_LIST),
+                                data.getBundle(DATA_OPTIONS));
+                        break;
+                    default:
+                        Log.w(TAG, "Unhandled message: " + msg
+                                + "\n  Client version: " + CLIENT_VERSION_CURRENT
+                                + "\n  Service version: " + msg.arg1);
+                }
+            } catch (BadParcelableException e) {
+                // Do not print the exception here, since it is already done by the Parcel class.
+                Log.e(TAG, "Could not unparcel the data.");
+                // If an error happened while connecting, disconnect from the service.
+                if (msg.what == SERVICE_MSG_ON_CONNECT) {
+                    serviceCallback.onConnectionFailed(callbacksMessenger);
+                }
+            }
+        }
+
+        void setCallbacksMessenger(Messenger callbacksMessenger) {
+            mCallbacksMessengerRef = new WeakReference<>(callbacksMessenger);
+        }
+    }
+
+    private static class ServiceBinderWrapper {
+        private Messenger mMessenger;
+        private Bundle mRootHints;
+
+        public ServiceBinderWrapper(IBinder target, Bundle rootHints) {
+            mMessenger = new Messenger(target);
+            mRootHints = rootHints;
+        }
+
+        void connect(Context context, Messenger callbacksMessenger)
+                throws RemoteException {
+            Bundle data = new Bundle();
+            data.putString(DATA_PACKAGE_NAME, context.getPackageName());
+            data.putBundle(DATA_ROOT_HINTS, mRootHints);
+            sendRequest(CLIENT_MSG_CONNECT, data, callbacksMessenger);
+        }
+
+        void disconnect(Messenger callbacksMessenger) throws RemoteException {
+            sendRequest(CLIENT_MSG_DISCONNECT, null, callbacksMessenger);
+        }
+
+        void addSubscription(String parentId, IBinder callbackToken, Bundle options,
+                Messenger callbacksMessenger)
+                throws RemoteException {
+            Bundle data = new Bundle();
+            data.putString(DATA_MEDIA_ITEM_ID, parentId);
+            BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
+            data.putBundle(DATA_OPTIONS, options);
+            sendRequest(CLIENT_MSG_ADD_SUBSCRIPTION, data, callbacksMessenger);
+        }
+
+        void removeSubscription(String parentId, IBinder callbackToken,
+                Messenger callbacksMessenger)
+                throws RemoteException {
+            Bundle data = new Bundle();
+            data.putString(DATA_MEDIA_ITEM_ID, parentId);
+            BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
+            sendRequest(CLIENT_MSG_REMOVE_SUBSCRIPTION, data, callbacksMessenger);
+        }
+
+        void getMediaItem(String mediaId, ResultReceiver receiver, Messenger callbacksMessenger)
+                throws RemoteException {
+            Bundle data = new Bundle();
+            data.putString(DATA_MEDIA_ITEM_ID, mediaId);
+            data.putParcelable(DATA_RESULT_RECEIVER, receiver);
+            sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, data, callbacksMessenger);
+        }
+
+        void registerCallbackMessenger(Messenger callbackMessenger) throws RemoteException {
+            Bundle data = new Bundle();
+            data.putBundle(DATA_ROOT_HINTS, mRootHints);
+            sendRequest(CLIENT_MSG_REGISTER_CALLBACK_MESSENGER, data, callbackMessenger);
+        }
+
+        void unregisterCallbackMessenger(Messenger callbackMessenger) throws RemoteException {
+            sendRequest(CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER, null, callbackMessenger);
+        }
+
+        void search(String query, Bundle extras, ResultReceiver receiver,
+                Messenger callbacksMessenger) throws RemoteException {
+            Bundle data = new Bundle();
+            data.putString(DATA_SEARCH_QUERY, query);
+            data.putBundle(DATA_SEARCH_EXTRAS, extras);
+            data.putParcelable(DATA_RESULT_RECEIVER, receiver);
+            sendRequest(CLIENT_MSG_SEARCH, data, callbacksMessenger);
+        }
+
+        void sendCustomAction(String action, Bundle extras, ResultReceiver receiver,
+                Messenger callbacksMessenger) throws RemoteException {
+            Bundle data = new Bundle();
+            data.putString(DATA_CUSTOM_ACTION, action);
+            data.putBundle(DATA_CUSTOM_ACTION_EXTRAS, extras);
+            data.putParcelable(DATA_RESULT_RECEIVER, receiver);
+            sendRequest(CLIENT_MSG_SEND_CUSTOM_ACTION, data, callbacksMessenger);
+        }
+
+        private void sendRequest(int what, Bundle data, Messenger cbMessenger)
+                throws RemoteException {
+            Message msg = Message.obtain();
+            msg.what = what;
+            msg.arg1 = CLIENT_VERSION_CURRENT;
+            msg.setData(data);
+            msg.replyTo = cbMessenger;
+            mMessenger.send(msg);
+        }
+    }
+
+    private  static class ItemReceiver extends ResultReceiver {
+        private final String mMediaId;
+        private final ItemCallback mCallback;
+
+        ItemReceiver(String mediaId, ItemCallback callback, Handler handler) {
+            super(handler);
+            mMediaId = mediaId;
+            mCallback = callback;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (resultData != null) {
+                resultData.setClassLoader(MediaBrowserCompat.class.getClassLoader());
+            }
+            if (resultCode != MediaBrowserServiceCompat.RESULT_OK || resultData == null
+                    || !resultData.containsKey(MediaBrowserServiceCompat.KEY_MEDIA_ITEM)) {
+                mCallback.onError(mMediaId);
+                return;
+            }
+            Parcelable item = resultData.getParcelable(MediaBrowserServiceCompat.KEY_MEDIA_ITEM);
+            if (item == null || item instanceof MediaItem) {
+                mCallback.onItemLoaded((MediaItem) item);
+            } else {
+                mCallback.onError(mMediaId);
+            }
+        }
+    }
+
+    private static class SearchResultReceiver extends ResultReceiver {
+        private final String mQuery;
+        private final Bundle mExtras;
+        private final SearchCallback mCallback;
+
+        SearchResultReceiver(String query, Bundle extras, SearchCallback callback,
+                Handler handler) {
+            super(handler);
+            mQuery = query;
+            mExtras = extras;
+            mCallback = callback;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (resultData != null) {
+                resultData.setClassLoader(MediaBrowserCompat.class.getClassLoader());
+            }
+            if (resultCode != MediaBrowserServiceCompat.RESULT_OK || resultData == null
+                    || !resultData.containsKey(MediaBrowserServiceCompat.KEY_SEARCH_RESULTS)) {
+                mCallback.onError(mQuery, mExtras);
+                return;
+            }
+            Parcelable[] items = resultData.getParcelableArray(
+                    MediaBrowserServiceCompat.KEY_SEARCH_RESULTS);
+            List<MediaItem> results = null;
+            if (items != null) {
+                results = new ArrayList<>();
+                for (Parcelable item : items) {
+                    results.add((MediaItem) item);
+                }
+            }
+            mCallback.onSearchResult(mQuery, mExtras, results);
+        }
+    }
+
+    private static class CustomActionResultReceiver extends ResultReceiver {
+        private final String mAction;
+        private final Bundle mExtras;
+        private final CustomActionCallback mCallback;
+
+        CustomActionResultReceiver(String action, Bundle extras, CustomActionCallback callback,
+                Handler handler) {
+            super(handler);
+            mAction = action;
+            mExtras = extras;
+            mCallback = callback;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (mCallback == null) {
+                return;
+            }
+            switch (resultCode) {
+                case MediaBrowserServiceCompat.RESULT_PROGRESS_UPDATE:
+                    mCallback.onProgressUpdate(mAction, mExtras, resultData);
+                    break;
+                case MediaBrowserServiceCompat.RESULT_OK:
+                    mCallback.onResult(mAction, mExtras, resultData);
+                    break;
+                case MediaBrowserServiceCompat.RESULT_ERROR:
+                    mCallback.onError(mAction, mExtras, resultData);
+                    break;
+                default:
+                    Log.w(TAG, "Unknown result code: " + resultCode + " (extras=" + mExtras
+                            + ", resultData=" + resultData + ")");
+                    break;
+            }
+        }
+    }
+}
diff --git a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
index 3403427..723cb3b 100644
--- a/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaDescriptionCompat.java
@@ -27,7 +27,6 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.media.MediaBrowserCompat;
 
 /**
  * A simple set of metadata for a media item suitable for display. This can be
diff --git a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
index be5e07c..435d78e 100644
--- a/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaMetadataCompat.java
@@ -23,14 +23,13 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.v4.media.session.MediaControllerCompat.TransportControls;
 import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.RestrictTo;
 import androidx.annotation.StringDef;
 import androidx.collection.ArrayMap;
-import androidx.media.MediaBrowserCompat;
-import androidx.media.session.MediaControllerCompat.TransportControls;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
new file mode 100644
index 0000000..5e6f4ea
--- /dev/null
+++ b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -0,0 +1,2489 @@
+/*
+ * Copyright 2018 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.support.v4.media.session;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.core.app.BundleCompat;
+import androidx.core.app.SupportActivity;
+import androidx.media.VolumeProviderCompat;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Allows an app to interact with an ongoing media session. Media buttons and
+ * other commands can be sent to the session. A callback may be registered to
+ * receive updates from the session, such as metadata and play state changes.
+ * <p>
+ * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
+ * from the session owner.
+ * <p>
+ * MediaController objects are thread-safe.
+ * <p>
+ * This is a helper for accessing features in {@link android.media.session.MediaSession}
+ * introduced after API level 4 in a backwards compatible fashion.
+ * <p class="note">
+ * If MediaControllerCompat is created with a {@link MediaSessionCompat.Token session token}
+ * from another process, following methods will not work directly after the creation if the
+ * {@link MediaSessionCompat.Token session token} is not passed through a
+ * {@link MediaBrowserCompat}:
+ * <ul>
+ * <li>{@link #getPlaybackState()}.{@link PlaybackStateCompat#getExtras() getExtras()}</li>
+ * <li>{@link #isCaptioningEnabled()}</li>
+ * <li>{@link #getRepeatMode()}</li>
+ * <li>{@link #getShuffleMode()}</li>
+ * </ul></p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about building your media application, read the
+ * <a href="{@docRoot}guide/topics/media-apps/index.html">Media Apps</a> developer guide.</p>
+ * </div>
+ */
+public final class MediaControllerCompat {
+    static final String TAG = "MediaControllerCompat";
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final String COMMAND_GET_EXTRA_BINDER =
+            "android.support.v4.media.session.command.GET_EXTRA_BINDER";
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final String COMMAND_ADD_QUEUE_ITEM =
+            "android.support.v4.media.session.command.ADD_QUEUE_ITEM";
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final String COMMAND_ADD_QUEUE_ITEM_AT =
+            "android.support.v4.media.session.command.ADD_QUEUE_ITEM_AT";
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final String COMMAND_REMOVE_QUEUE_ITEM =
+            "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM";
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final String COMMAND_REMOVE_QUEUE_ITEM_AT =
+            "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM_AT";
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final String COMMAND_ARGUMENT_MEDIA_DESCRIPTION =
+            "android.support.v4.media.session.command.ARGUMENT_MEDIA_DESCRIPTION";
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final String COMMAND_ARGUMENT_INDEX =
+            "android.support.v4.media.session.command.ARGUMENT_INDEX";
+
+    private static class MediaControllerExtraData extends SupportActivity.ExtraData {
+        private final MediaControllerCompat mMediaController;
+
+        MediaControllerExtraData(MediaControllerCompat mediaController) {
+            mMediaController = mediaController;
+        }
+
+        MediaControllerCompat getMediaController() {
+            return mMediaController;
+        }
+    }
+
+    /**
+     * Sets a {@link MediaControllerCompat} in the {@code activity} for later retrieval via
+     * {@link #getMediaController(Activity)}.
+     *
+     * <p>This is compatible with {@link Activity#setMediaController(MediaController)}.
+     * If {@code activity} inherits {@link androidx.fragment.app.FragmentActivity}, the
+     * {@code mediaController} will be saved in the {@code activity}. In addition to that,
+     * on API 21 and later, {@link Activity#setMediaController(MediaController)} will be
+     * called.</p>
+     *
+     * @param activity The activity to set the {@code mediaController} in, must not be null.
+     * @param mediaController The controller for the session which should receive
+     *     media keys and volume changes on API 21 and later.
+     * @see #getMediaController(Activity)
+     * @see Activity#setMediaController(android.media.session.MediaController)
+     */
+    public static void setMediaController(@NonNull Activity activity,
+            MediaControllerCompat mediaController) {
+        if (activity instanceof SupportActivity) {
+            ((SupportActivity) activity).putExtraData(
+                    new MediaControllerExtraData(mediaController));
+        }
+        if (android.os.Build.VERSION.SDK_INT >= 21) {
+            Object controllerObj = null;
+            if (mediaController != null) {
+                Object sessionTokenObj = mediaController.getSessionToken().getToken();
+                controllerObj = MediaControllerCompatApi21.fromToken(activity, sessionTokenObj);
+            }
+            MediaControllerCompatApi21.setMediaController(activity, controllerObj);
+        }
+    }
+
+    /**
+     * Retrieves the {@link MediaControllerCompat} set in the activity by
+     * {@link #setMediaController(Activity, MediaControllerCompat)} for sending media key and volume
+     * events.
+     *
+     * <p>This is compatible with {@link Activity#getMediaController()}.</p>
+     *
+     * @param activity The activity to get the media controller from, must not be null.
+     * @return The controller which should receive events.
+     * @see #setMediaController(Activity, MediaControllerCompat)
+     */
+    public static MediaControllerCompat getMediaController(@NonNull Activity activity) {
+        if (activity instanceof SupportActivity) {
+            MediaControllerExtraData extraData =
+                    ((SupportActivity) activity).getExtraData(MediaControllerExtraData.class);
+            return extraData != null ? extraData.getMediaController() : null;
+        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+            Object controllerObj = MediaControllerCompatApi21.getMediaController(activity);
+            if (controllerObj == null) {
+                return null;
+            }
+            Object sessionTokenObj = MediaControllerCompatApi21.getSessionToken(controllerObj);
+            try {
+                return new MediaControllerCompat(activity,
+                        MediaSessionCompat.Token.fromToken(sessionTokenObj));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getMediaController.", e);
+            }
+        }
+        return null;
+    }
+
+    private static void validateCustomAction(String action, Bundle args) {
+        if (action == null) {
+            return;
+        }
+        switch(action) {
+            case MediaSessionCompat.ACTION_FOLLOW:
+            case MediaSessionCompat.ACTION_UNFOLLOW:
+                if (args == null
+                        || !args.containsKey(MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE)) {
+                    throw new IllegalArgumentException("An extra field "
+                            + MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE + " is required "
+                            + "for this action " + action + ".");
+                }
+                break;
+        }
+    }
+
+    private final MediaControllerImpl mImpl;
+    private final MediaSessionCompat.Token mToken;
+    // This set is used to keep references to registered callbacks to prevent them being GCed,
+    // since we only keep weak references for callbacks in this class and its inner classes.
+    private final HashSet<Callback> mRegisteredCallbacks = new HashSet<>();
+
+    /**
+     * Creates a media controller from a session.
+     *
+     * @param session The session to be controlled.
+     */
+    public MediaControllerCompat(Context context, @NonNull MediaSessionCompat session) {
+        if (session == null) {
+            throw new IllegalArgumentException("session must not be null");
+        }
+        mToken = session.getSessionToken();
+
+        if (android.os.Build.VERSION.SDK_INT >= 24) {
+            mImpl = new MediaControllerImplApi24(context, session);
+        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
+            mImpl = new MediaControllerImplApi23(context, session);
+        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+            mImpl = new MediaControllerImplApi21(context, session);
+        } else {
+            mImpl = new MediaControllerImplBase(mToken);
+        }
+    }
+
+    /**
+     * Creates a media controller from a session token which may have
+     * been obtained from another process.
+     *
+     * @param sessionToken The token of the session to be controlled.
+     * @throws RemoteException if the session is not accessible.
+     */
+    public MediaControllerCompat(Context context, @NonNull MediaSessionCompat.Token sessionToken)
+            throws RemoteException {
+        if (sessionToken == null) {
+            throw new IllegalArgumentException("sessionToken must not be null");
+        }
+        mToken = sessionToken;
+
+        if (android.os.Build.VERSION.SDK_INT >= 24) {
+            mImpl = new MediaControllerImplApi24(context, sessionToken);
+        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
+            mImpl = new MediaControllerImplApi23(context, sessionToken);
+        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+            mImpl = new MediaControllerImplApi21(context, sessionToken);
+        } else {
+            mImpl = new MediaControllerImplBase(mToken);
+        }
+    }
+
+    /**
+     * Gets a {@link TransportControls} instance for this session.
+     *
+     * @return A controls instance
+     */
+    public TransportControls getTransportControls() {
+        return mImpl.getTransportControls();
+    }
+
+    /**
+     * Sends the specified media button event to the session. Only media keys can
+     * be sent by this method, other keys will be ignored.
+     *
+     * @param keyEvent The media button event to dispatch.
+     * @return true if the event was sent to the session, false otherwise.
+     */
+    public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
+        if (keyEvent == null) {
+            throw new IllegalArgumentException("KeyEvent may not be null");
+        }
+        return mImpl.dispatchMediaButtonEvent(keyEvent);
+    }
+
+    /**
+     * Gets the current playback state for this session.
+     *
+     * <p>If the session is not ready, {@link PlaybackStateCompat#getExtras()} on the result of
+     * this method may return null. </p>
+     *
+     * @return The current PlaybackState or null
+     * @see #isSessionReady
+     * @see Callback#onSessionReady
+     */
+    public PlaybackStateCompat getPlaybackState() {
+        return mImpl.getPlaybackState();
+    }
+
+    /**
+     * Gets the current metadata for this session.
+     *
+     * @return The current MediaMetadata or null.
+     */
+    public MediaMetadataCompat getMetadata() {
+        return mImpl.getMetadata();
+    }
+
+    /**
+     * Gets the current play queue for this session if one is set. If you only
+     * care about the current item {@link #getMetadata()} should be used.
+     *
+     * @return The current play queue or null.
+     */
+    public List<QueueItem> getQueue() {
+        return mImpl.getQueue();
+    }
+
+    /**
+     * Adds a queue item from the given {@code description} at the end of the play queue
+     * of this session. Not all sessions may support this. To know whether the session supports
+     * this, get the session's flags with {@link #getFlags()} and check that the flag
+     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
+     *
+     * @param description The {@link MediaDescriptionCompat} for creating the
+     *            {@link MediaSessionCompat.QueueItem} to be inserted.
+     * @throws UnsupportedOperationException If this session doesn't support this.
+     * @see #getFlags()
+     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
+     */
+    public void addQueueItem(MediaDescriptionCompat description) {
+        mImpl.addQueueItem(description);
+    }
+
+    /**
+     * Adds a queue item from the given {@code description} at the specified position
+     * in the play queue of this session. Shifts the queue item currently at that position
+     * (if any) and any subsequent queue items to the right (adds one to their indices).
+     * Not all sessions may support this. To know whether the session supports this,
+     * get the session's flags with {@link #getFlags()} and check that the flag
+     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
+     *
+     * @param description The {@link MediaDescriptionCompat} for creating the
+     *            {@link MediaSessionCompat.QueueItem} to be inserted.
+     * @param index The index at which the created {@link MediaSessionCompat.QueueItem}
+     *            is to be inserted.
+     * @throws UnsupportedOperationException If this session doesn't support this.
+     * @see #getFlags()
+     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
+     */
+    public void addQueueItem(MediaDescriptionCompat description, int index) {
+        mImpl.addQueueItem(description, index);
+    }
+
+    /**
+     * Removes the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
+     * with the given {@link MediaDescriptionCompat description} in the play queue of the
+     * associated session. Not all sessions may support this. To know whether the session supports
+     * this, get the session's flags with {@link #getFlags()} and check that the flag
+     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
+     *
+     * @param description The {@link MediaDescriptionCompat} for denoting the
+     *            {@link MediaSessionCompat.QueueItem} to be removed.
+     * @throws UnsupportedOperationException If this session doesn't support this.
+     * @see #getFlags()
+     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
+     */
+    public void removeQueueItem(MediaDescriptionCompat description) {
+        mImpl.removeQueueItem(description);
+    }
+
+    /**
+     * Removes an queue item at the specified position in the play queue
+     * of this session. Not all sessions may support this. To know whether the session supports
+     * this, get the session's flags with {@link #getFlags()} and check that the flag
+     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
+     *
+     * @param index The index of the element to be removed.
+     * @throws UnsupportedOperationException If this session doesn't support this.
+     * @see #getFlags()
+     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
+     * @deprecated Use {@link #removeQueueItem(MediaDescriptionCompat)} instead.
+     */
+    @Deprecated
+    public void removeQueueItemAt(int index) {
+        List<QueueItem> queue = getQueue();
+        if (queue != null && index >= 0 && index < queue.size()) {
+            QueueItem item = queue.get(index);
+            if (item != null) {
+                removeQueueItem(item.getDescription());
+            }
+        }
+    }
+
+    /**
+     * Gets the queue title for this session.
+     */
+    public CharSequence getQueueTitle() {
+        return mImpl.getQueueTitle();
+    }
+
+    /**
+     * Gets the extras for this session.
+     */
+    public Bundle getExtras() {
+        return mImpl.getExtras();
+    }
+
+    /**
+     * Gets the rating type supported by the session. One of:
+     * <ul>
+     * <li>{@link RatingCompat#RATING_NONE}</li>
+     * <li>{@link RatingCompat#RATING_HEART}</li>
+     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
+     * <li>{@link RatingCompat#RATING_3_STARS}</li>
+     * <li>{@link RatingCompat#RATING_4_STARS}</li>
+     * <li>{@link RatingCompat#RATING_5_STARS}</li>
+     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
+     * </ul>
+     * <p>If the session is not ready, it will return {@link RatingCompat#RATING_NONE}.</p>
+     *
+     * @return The supported rating type, or {@link RatingCompat#RATING_NONE} if the value is not
+     *         set or the session is not ready.
+     * @see #isSessionReady
+     * @see Callback#onSessionReady
+     */
+    public int getRatingType() {
+        return mImpl.getRatingType();
+    }
+
+    /**
+     * Returns whether captioning is enabled for this session.
+     *
+     * <p>If the session is not ready, it will return a {@code false}.</p>
+     *
+     * @return {@code true} if captioning is enabled, {@code false} if disabled or not set.
+     * @see #isSessionReady
+     * @see Callback#onSessionReady
+     */
+    public boolean isCaptioningEnabled() {
+        return mImpl.isCaptioningEnabled();
+    }
+
+    /**
+     * Gets the repeat mode for this session.
+     *
+     * @return The latest repeat mode set to the session,
+     *         {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set, or
+     *         {@link PlaybackStateCompat#REPEAT_MODE_INVALID} if the session is not ready yet.
+     * @see #isSessionReady
+     * @see Callback#onSessionReady
+     */
+    public int getRepeatMode() {
+        return mImpl.getRepeatMode();
+    }
+
+    /**
+     * Gets the shuffle mode for this session.
+     *
+     * @return The latest shuffle mode set to the session, or
+     *         {@link PlaybackStateCompat#SHUFFLE_MODE_NONE} if disabled or not set, or
+     *         {@link PlaybackStateCompat#SHUFFLE_MODE_INVALID} if the session is not ready yet.
+     * @see #isSessionReady
+     * @see Callback#onSessionReady
+     */
+    public int getShuffleMode() {
+        return mImpl.getShuffleMode();
+    }
+
+    /**
+     * Gets the flags for this session. Flags are defined in
+     * {@link MediaSessionCompat}.
+     *
+     * @return The current set of flags for the session.
+     */
+    public long getFlags() {
+        return mImpl.getFlags();
+    }
+
+    /**
+     * Gets the current playback info for this session.
+     *
+     * @return The current playback info or null.
+     */
+    public PlaybackInfo getPlaybackInfo() {
+        return mImpl.getPlaybackInfo();
+    }
+
+    /**
+     * Gets an intent for launching UI associated with this session if one
+     * exists.
+     *
+     * @return A {@link PendingIntent} to launch UI or null.
+     */
+    public PendingIntent getSessionActivity() {
+        return mImpl.getSessionActivity();
+    }
+
+    /**
+     * Gets the token for the session this controller is connected to.
+     *
+     * @return The session's token.
+     */
+    public MediaSessionCompat.Token getSessionToken() {
+        return mToken;
+    }
+
+    /**
+     * Sets the volume of the output this session is playing on. The command will
+     * be ignored if it does not support
+     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
+     * {@link AudioManager} may be used to affect the handling.
+     *
+     * @see #getPlaybackInfo()
+     * @param value The value to set it to, between 0 and the reported max.
+     * @param flags Flags from {@link AudioManager} to include with the volume
+     *            request.
+     */
+    public void setVolumeTo(int value, int flags) {
+        mImpl.setVolumeTo(value, flags);
+    }
+
+    /**
+     * Adjusts the volume of the output this session is playing on. The direction
+     * must be one of {@link AudioManager#ADJUST_LOWER},
+     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+     * The command will be ignored if the session does not support
+     * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
+     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
+     * {@link AudioManager} may be used to affect the handling.
+     *
+     * @see #getPlaybackInfo()
+     * @param direction The direction to adjust the volume in.
+     * @param flags Any flags to pass with the command.
+     */
+    public void adjustVolume(int direction, int flags) {
+        mImpl.adjustVolume(direction, flags);
+    }
+
+    /**
+     * Adds a callback to receive updates from the Session. Updates will be
+     * posted on the caller's thread.
+     *
+     * @param callback The callback object, must not be null.
+     */
+    public void registerCallback(@NonNull Callback callback) {
+        registerCallback(callback, null);
+    }
+
+    /**
+     * Adds a callback to receive updates from the session. Updates will be
+     * posted on the specified handler's thread.
+     *
+     * @param callback The callback object, must not be null.
+     * @param handler The handler to post updates on. If null the callers thread
+     *            will be used.
+     */
+    public void registerCallback(@NonNull Callback callback, Handler handler) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+        if (handler == null) {
+            handler = new Handler();
+        }
+        callback.setHandler(handler);
+        mImpl.registerCallback(callback, handler);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    /**
+     * Stops receiving updates on the specified callback. If an update has
+     * already been posted you may still receive it after calling this method.
+     *
+     * @param callback The callback to remove
+     */
+    public void unregisterCallback(@NonNull Callback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+        try {
+            mRegisteredCallbacks.remove(callback);
+            mImpl.unregisterCallback(callback);
+        } finally {
+            callback.setHandler(null);
+        }
+    }
+
+    /**
+     * Sends a generic command to the session. It is up to the session creator
+     * to decide what commands and parameters they will support. As such,
+     * commands should only be sent to sessions that the controller owns.
+     *
+     * @param command The command to send
+     * @param params Any parameters to include with the command
+     * @param cb The callback to receive the result on
+     */
+    public void sendCommand(@NonNull String command, Bundle params, ResultReceiver cb) {
+        if (TextUtils.isEmpty(command)) {
+            throw new IllegalArgumentException("command must neither be null nor empty");
+        }
+        mImpl.sendCommand(command, params, cb);
+    }
+
+    /**
+     * Returns whether the session is ready or not.
+     *
+     * <p>If the session is not ready, following methods can work incorrectly.</p>
+     * <ul>
+     * <li>{@link #getPlaybackState()}</li>
+     * <li>{@link #getRatingType()}</li>
+     * <li>{@link #getRepeatMode()}</li>
+     * <li>{@link #getShuffleMode()}</li>
+     * <li>{@link #isCaptioningEnabled()}</li>
+     * </ul>
+     *
+     * @return true if the session is ready, false otherwise.
+     * @see Callback#onSessionReady()
+     */
+    public boolean isSessionReady() {
+        return mImpl.isSessionReady();
+    }
+
+    /**
+     * Gets the session owner's package name.
+     *
+     * @return The package name of of the session owner.
+     */
+    public String getPackageName() {
+        return mImpl.getPackageName();
+    }
+
+    /**
+     * Gets the underlying framework
+     * {@link android.media.session.MediaController} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @return The underlying {@link android.media.session.MediaController}
+     *         object, or null if none.
+     */
+    public Object getMediaController() {
+        return mImpl.getMediaController();
+    }
+
+    /**
+     * Callback for receiving updates on from the session. A Callback can be
+     * registered using {@link #registerCallback}
+     */
+    public static abstract class Callback implements IBinder.DeathRecipient {
+        private final Object mCallbackObj;
+        MessageHandler mHandler;
+        boolean mHasExtraCallback;
+
+        public Callback() {
+            if (android.os.Build.VERSION.SDK_INT >= 21) {
+                mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21(this));
+            } else {
+                mCallbackObj = new StubCompat(this);
+            }
+        }
+
+        /**
+         * Override to handle the session being ready.
+         *
+         * @see MediaControllerCompat#isSessionReady
+         */
+        public void onSessionReady() {
+        }
+
+        /**
+         * Override to handle the session being destroyed. The session is no
+         * longer valid after this call and calls to it will be ignored.
+         */
+        public void onSessionDestroyed() {
+        }
+
+        /**
+         * Override to handle custom events sent by the session owner without a
+         * specified interface. Controllers should only handle these for
+         * sessions they own.
+         *
+         * @param event The event from the session.
+         * @param extras Optional parameters for the event.
+         */
+        public void onSessionEvent(String event, Bundle extras) {
+        }
+
+        /**
+         * Override to handle changes in playback state.
+         *
+         * @param state The new playback state of the session
+         */
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+        }
+
+        /**
+         * Override to handle changes to the current metadata.
+         *
+         * @param metadata The current metadata for the session or null if none.
+         * @see MediaMetadataCompat
+         */
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+        }
+
+        /**
+         * Override to handle changes to items in the queue.
+         *
+         * @see MediaSessionCompat.QueueItem
+         * @param queue A list of items in the current play queue. It should
+         *            include the currently playing item as well as previous and
+         *            upcoming items if applicable.
+         */
+        public void onQueueChanged(List<QueueItem> queue) {
+        }
+
+        /**
+         * Override to handle changes to the queue title.
+         *
+         * @param title The title that should be displayed along with the play
+         *            queue such as "Now Playing". May be null if there is no
+         *            such title.
+         */
+        public void onQueueTitleChanged(CharSequence title) {
+        }
+
+        /**
+         * Override to handle changes to the {@link MediaSessionCompat} extras.
+         *
+         * @param extras The extras that can include other information
+         *            associated with the {@link MediaSessionCompat}.
+         */
+        public void onExtrasChanged(Bundle extras) {
+        }
+
+        /**
+         * Override to handle changes to the audio info.
+         *
+         * @param info The current audio info for this session.
+         */
+        public void onAudioInfoChanged(PlaybackInfo info) {
+        }
+
+        /**
+         * Override to handle changes to the captioning enabled status.
+         *
+         * @param enabled {@code true} if captioning is enabled, {@code false} otherwise.
+         */
+        public void onCaptioningEnabledChanged(boolean enabled) {
+        }
+
+        /**
+         * Override to handle changes to the repeat mode.
+         *
+         * @param repeatMode The repeat mode. It should be one of followings:
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
+         */
+        public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode int repeatMode) {
+        }
+
+        /**
+         * Override to handle changes to the shuffle mode.
+         *
+         * @param shuffleMode The shuffle mode. Must be one of the followings:
+         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
+         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
+         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
+         */
+        public void onShuffleModeChanged(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
+        }
+
+        @Override
+        public void binderDied() {
+            onSessionDestroyed();
+        }
+
+        /**
+         * Set the handler to use for callbacks.
+         */
+        void setHandler(Handler handler) {
+            if (handler == null) {
+                if (mHandler != null) {
+                    mHandler.mRegistered = false;
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler = null;
+                }
+            } else {
+                mHandler = new MessageHandler(handler.getLooper());
+                mHandler.mRegistered = true;
+            }
+        }
+
+        void postToHandler(int what, Object obj, Bundle data) {
+            if (mHandler != null) {
+                Message msg = mHandler.obtainMessage(what, obj);
+                msg.setData(data);
+                msg.sendToTarget();
+            }
+        }
+
+        private static class StubApi21 implements MediaControllerCompatApi21.Callback {
+            private final WeakReference<MediaControllerCompat.Callback> mCallback;
+
+            StubApi21(MediaControllerCompat.Callback callback) {
+                mCallback = new WeakReference<>(callback);
+            }
+
+            @Override
+            public void onSessionDestroyed() {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.onSessionDestroyed();
+                }
+            }
+
+            @Override
+            public void onSessionEvent(String event, Bundle extras) {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    if (callback.mHasExtraCallback && android.os.Build.VERSION.SDK_INT < 23) {
+                        // Ignore. ExtraCallback will handle this.
+                    } else {
+                        callback.onSessionEvent(event, extras);
+                    }
+                }
+            }
+
+            @Override
+            public void onPlaybackStateChanged(Object stateObj) {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    if (callback.mHasExtraCallback) {
+                        // Ignore. ExtraCallback will handle this.
+                    } else {
+                        callback.onPlaybackStateChanged(
+                                PlaybackStateCompat.fromPlaybackState(stateObj));
+                    }
+                }
+            }
+
+            @Override
+            public void onMetadataChanged(Object metadataObj) {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
+                }
+            }
+
+            @Override
+            public void onQueueChanged(List<?> queue) {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.onQueueChanged(QueueItem.fromQueueItemList(queue));
+                }
+            }
+
+            @Override
+            public void onQueueTitleChanged(CharSequence title) {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.onQueueTitleChanged(title);
+                }
+            }
+
+            @Override
+            public void onExtrasChanged(Bundle extras) {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.onExtrasChanged(extras);
+                }
+            }
+
+            @Override
+            public void onAudioInfoChanged(
+                    int type, int stream, int control, int max, int current) {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.onAudioInfoChanged(
+                            new PlaybackInfo(type, stream, control, max, current));
+                }
+            }
+        }
+
+        private static class StubCompat extends IMediaControllerCallback.Stub {
+            private final WeakReference<MediaControllerCompat.Callback> mCallback;
+
+            StubCompat(MediaControllerCompat.Callback callback) {
+                mCallback = new WeakReference<>(callback);
+            }
+
+            @Override
+            public void onEvent(String event, Bundle extras) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_EVENT, event, extras);
+                }
+            }
+
+            @Override
+            public void onSessionDestroyed() throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_DESTROYED, null, null);
+                }
+            }
+
+            @Override
+            public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
+                }
+            }
+
+            @Override
+            public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
+                }
+            }
+
+            @Override
+            public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
+                }
+            }
+
+            @Override
+            public void onQueueTitleChanged(CharSequence title) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
+                }
+            }
+
+            @Override
+            public void onCaptioningEnabledChanged(boolean enabled) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(
+                            MessageHandler.MSG_UPDATE_CAPTIONING_ENABLED, enabled, null);
+                }
+            }
+
+            @Override
+            public void onRepeatModeChanged(int repeatMode) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null);
+                }
+            }
+
+            @Override
+            public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException {
+                // Do nothing.
+            }
+
+            @Override
+            public void onShuffleModeChanged(int shuffleMode) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(
+                            MessageHandler.MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null);
+                }
+            }
+
+            @Override
+            public void onExtrasChanged(Bundle extras) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
+                }
+            }
+
+            @Override
+            public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    PlaybackInfo pi = null;
+                    if (info != null) {
+                        pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
+                                info.maxVolume, info.currentVolume);
+                    }
+                    callback.postToHandler(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
+                }
+            }
+
+            @Override
+            public void onSessionReady() throws RemoteException {
+                MediaControllerCompat.Callback callback = mCallback.get();
+                if (callback != null) {
+                    callback.postToHandler(MessageHandler.MSG_SESSION_READY, null, null);
+                }
+            }
+        }
+
+        private class MessageHandler extends Handler {
+            private static final int MSG_EVENT = 1;
+            private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
+            private static final int MSG_UPDATE_METADATA = 3;
+            private static final int MSG_UPDATE_VOLUME = 4;
+            private static final int MSG_UPDATE_QUEUE = 5;
+            private static final int MSG_UPDATE_QUEUE_TITLE = 6;
+            private static final int MSG_UPDATE_EXTRAS = 7;
+            private static final int MSG_DESTROYED = 8;
+            private static final int MSG_UPDATE_REPEAT_MODE = 9;
+            private static final int MSG_UPDATE_CAPTIONING_ENABLED = 11;
+            private static final int MSG_UPDATE_SHUFFLE_MODE = 12;
+            private static final int MSG_SESSION_READY = 13;
+
+            boolean mRegistered = false;
+
+            MessageHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (!mRegistered) {
+                    return;
+                }
+                switch (msg.what) {
+                    case MSG_EVENT:
+                        onSessionEvent((String) msg.obj, msg.getData());
+                        break;
+                    case MSG_UPDATE_PLAYBACK_STATE:
+                        onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
+                        break;
+                    case MSG_UPDATE_METADATA:
+                        onMetadataChanged((MediaMetadataCompat) msg.obj);
+                        break;
+                    case MSG_UPDATE_QUEUE:
+                        onQueueChanged((List<QueueItem>) msg.obj);
+                        break;
+                    case MSG_UPDATE_QUEUE_TITLE:
+                        onQueueTitleChanged((CharSequence) msg.obj);
+                        break;
+                    case MSG_UPDATE_CAPTIONING_ENABLED:
+                        onCaptioningEnabledChanged((boolean) msg.obj);
+                        break;
+                    case MSG_UPDATE_REPEAT_MODE:
+                        onRepeatModeChanged((int) msg.obj);
+                        break;
+                    case MSG_UPDATE_SHUFFLE_MODE:
+                        onShuffleModeChanged((int) msg.obj);
+                        break;
+                    case MSG_UPDATE_EXTRAS:
+                        onExtrasChanged((Bundle) msg.obj);
+                        break;
+                    case MSG_UPDATE_VOLUME:
+                        onAudioInfoChanged((PlaybackInfo) msg.obj);
+                        break;
+                    case MSG_DESTROYED:
+                        onSessionDestroyed();
+                        break;
+                    case MSG_SESSION_READY:
+                        onSessionReady();
+                        break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Interface for controlling media playback on a session. This allows an app
+     * to send media transport commands to the session.
+     */
+    public static abstract class TransportControls {
+        /**
+         * Used as an integer extra field in {@link #playFromMediaId(String, Bundle)} or
+         * {@link #prepareFromMediaId(String, Bundle)} to indicate the stream type to be used by the
+         * media player when playing or preparing the specified media id. See {@link AudioManager}
+         * for a list of stream types.
+         */
+        public static final String EXTRA_LEGACY_STREAM_TYPE =
+                "android.media.session.extra.LEGACY_STREAM_TYPE";
+
+        TransportControls() {
+        }
+
+        /**
+         * Request that the player prepare for playback. This can decrease the time it takes to
+         * start playback when a play command is received. Preparation is not required. You can
+         * call {@link #play} without calling this method beforehand.
+         */
+        public abstract void prepare();
+
+        /**
+         * Request that the player prepare playback for a specific media id. This can decrease the
+         * time it takes to start playback when a play command is received. Preparation is not
+         * required. You can call {@link #playFromMediaId} without calling this method beforehand.
+         *
+         * @param mediaId The id of the requested media.
+         * @param extras Optional extras that can include extra information about the media item
+         *               to be prepared.
+         */
+        public abstract void prepareFromMediaId(String mediaId, Bundle extras);
+
+        /**
+         * Request that the player prepare playback for a specific search query. This can decrease
+         * the time it takes to start playback when a play command is received. An empty or null
+         * query should be treated as a request to prepare any music. Preparation is not required.
+         * You can call {@link #playFromSearch} without calling this method beforehand.
+         *
+         * @param query The search query.
+         * @param extras Optional extras that can include extra information
+         *               about the query.
+         */
+        public abstract void prepareFromSearch(String query, Bundle extras);
+
+        /**
+         * Request that the player prepare playback for a specific {@link Uri}. This can decrease
+         * the time it takes to start playback when a play command is received. Preparation is not
+         * required. You can call {@link #playFromUri} without calling this method beforehand.
+         *
+         * @param uri The URI of the requested media.
+         * @param extras Optional extras that can include extra information about the media item
+         *               to be prepared.
+         */
+        public abstract void prepareFromUri(Uri uri, Bundle extras);
+
+        /**
+         * Request that the player start its playback at its current position.
+         */
+        public abstract void play();
+
+        /**
+         * Request that the player start playback for a specific {@link Uri}.
+         *
+         * @param mediaId The uri of the requested media.
+         * @param extras Optional extras that can include extra information
+         *            about the media item to be played.
+         */
+        public abstract void playFromMediaId(String mediaId, Bundle extras);
+
+        /**
+         * Request that the player start playback for a specific search query.
+         * An empty or null query should be treated as a request to play any
+         * music.
+         *
+         * @param query The search query.
+         * @param extras Optional extras that can include extra information
+         *            about the query.
+         */
+        public abstract void playFromSearch(String query, Bundle extras);
+
+        /**
+         * Request that the player start playback for a specific {@link Uri}.
+         *
+         * @param uri  The URI of the requested media.
+         * @param extras Optional extras that can include extra information about the media item
+         *               to be played.
+         */
+        public abstract void playFromUri(Uri uri, Bundle extras);
+
+        /**
+         * Plays an item with a specific id in the play queue. If you specify an
+         * id that is not in the play queue, the behavior is undefined.
+         */
+        public abstract void skipToQueueItem(long id);
+
+        /**
+         * Request that the player pause its playback and stay at its current
+         * position.
+         */
+        public abstract void pause();
+
+        /**
+         * Request that the player stop its playback; it may clear its state in
+         * whatever way is appropriate.
+         */
+        public abstract void stop();
+
+        /**
+         * Moves to a new location in the media stream.
+         *
+         * @param pos Position to move to, in milliseconds.
+         */
+        public abstract void seekTo(long pos);
+
+        /**
+         * Starts fast forwarding. If playback is already fast forwarding this
+         * may increase the rate.
+         */
+        public abstract void fastForward();
+
+        /**
+         * Skips to the next item.
+         */
+        public abstract void skipToNext();
+
+        /**
+         * Starts rewinding. If playback is already rewinding this may increase
+         * the rate.
+         */
+        public abstract void rewind();
+
+        /**
+         * Skips to the previous item.
+         */
+        public abstract void skipToPrevious();
+
+        /**
+         * Rates the current content. This will cause the rating to be set for
+         * the current user. The rating type of the given {@link RatingCompat} must match the type
+         * returned by {@link #getRatingType()}.
+         *
+         * @param rating The rating to set for the current content
+         */
+        public abstract void setRating(RatingCompat rating);
+
+        /**
+         * Rates a media item. This will cause the rating to be set for
+         * the specific media item. The rating type of the given {@link RatingCompat} must match
+         * the type returned by {@link #getRatingType()}.
+         *
+         * @param rating The rating to set for the media item.
+         * @param extras Optional arguments that can include information about the media item
+         *               to be rated.
+         *
+         * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE
+         * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE_VALUE
+         */
+        public abstract void setRating(RatingCompat rating, Bundle extras);
+
+        /**
+         * Enables/disables captioning for this session.
+         *
+         * @param enabled {@code true} to enable captioning, {@code false} to disable.
+         */
+        public abstract void setCaptioningEnabled(boolean enabled);
+
+        /**
+         * Sets the repeat mode for this session.
+         *
+         * @param repeatMode The repeat mode. Must be one of the followings:
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
+         *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
+         */
+        public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
+
+        /**
+         * Sets the shuffle mode for this session.
+         *
+         * @param shuffleMode The shuffle mode. Must be one of the followings:
+         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
+         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
+         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
+         */
+        public abstract void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
+
+        /**
+         * Sends a custom action for the {@link MediaSessionCompat} to perform.
+         *
+         * @param customAction The action to perform.
+         * @param args Optional arguments to supply to the
+         *            {@link MediaSessionCompat} for this custom action.
+         */
+        public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
+                Bundle args);
+
+        /**
+         * Sends the id and args from a custom action for the
+         * {@link MediaSessionCompat} to perform.
+         *
+         * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
+         *      Bundle args)
+         * @see MediaSessionCompat#ACTION_FLAG_AS_INAPPROPRIATE
+         * @see MediaSessionCompat#ACTION_SKIP_AD
+         * @see MediaSessionCompat#ACTION_FOLLOW
+         * @see MediaSessionCompat#ACTION_UNFOLLOW
+         * @param action The action identifier of the
+         *            {@link PlaybackStateCompat.CustomAction} as specified by
+         *            the {@link MediaSessionCompat}.
+         * @param args Optional arguments to supply to the
+         *            {@link MediaSessionCompat} for this custom action.
+         */
+        public abstract void sendCustomAction(String action, Bundle args);
+    }
+
+    /**
+     * Holds information about the way volume is handled for this session.
+     */
+    public static final class PlaybackInfo {
+        /**
+         * The session uses local playback.
+         */
+        public static final int PLAYBACK_TYPE_LOCAL = 1;
+        /**
+         * The session uses remote playback.
+         */
+        public static final int PLAYBACK_TYPE_REMOTE = 2;
+
+        private final int mPlaybackType;
+        // TODO update audio stream with AudioAttributes support version
+        private final int mAudioStream;
+        private final int mVolumeControl;
+        private final int mMaxVolume;
+        private final int mCurrentVolume;
+
+        PlaybackInfo(int type, int stream, int control, int max, int current) {
+            mPlaybackType = type;
+            mAudioStream = stream;
+            mVolumeControl = control;
+            mMaxVolume = max;
+            mCurrentVolume = current;
+        }
+
+        /**
+         * Gets the type of volume handling, either local or remote. One of:
+         * <ul>
+         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
+         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
+         * </ul>
+         *
+         * @return The type of volume handling this session is using.
+         */
+        public int getPlaybackType() {
+            return mPlaybackType;
+        }
+
+        /**
+         * Gets the stream this is currently controlling volume on. When the volume
+         * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
+         * have meaning and should be ignored.
+         *
+         * @return The stream this session is playing on.
+         */
+        public int getAudioStream() {
+            // TODO switch to AudioAttributesCompat when it is added.
+            return mAudioStream;
+        }
+
+        /**
+         * Gets the type of volume control that can be used. One of:
+         * <ul>
+         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
+         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
+         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
+         * </ul>
+         *
+         * @return The type of volume control that may be used with this
+         *         session.
+         */
+        public int getVolumeControl() {
+            return mVolumeControl;
+        }
+
+        /**
+         * Gets the maximum volume that may be set for this session.
+         *
+         * @return The maximum allowed volume where this session is playing.
+         */
+        public int getMaxVolume() {
+            return mMaxVolume;
+        }
+
+        /**
+         * Gets the current volume for this session.
+         *
+         * @return The current volume where this session is playing.
+         */
+        public int getCurrentVolume() {
+            return mCurrentVolume;
+        }
+    }
+
+    interface MediaControllerImpl {
+        void registerCallback(Callback callback, Handler handler);
+
+        void unregisterCallback(Callback callback);
+        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
+        TransportControls getTransportControls();
+        PlaybackStateCompat getPlaybackState();
+        MediaMetadataCompat getMetadata();
+
+        List<QueueItem> getQueue();
+        void addQueueItem(MediaDescriptionCompat description);
+        void addQueueItem(MediaDescriptionCompat description, int index);
+        void removeQueueItem(MediaDescriptionCompat description);
+        CharSequence getQueueTitle();
+        Bundle getExtras();
+        int getRatingType();
+        boolean isCaptioningEnabled();
+        int getRepeatMode();
+        int getShuffleMode();
+        long getFlags();
+        PlaybackInfo getPlaybackInfo();
+        PendingIntent getSessionActivity();
+
+        void setVolumeTo(int value, int flags);
+        void adjustVolume(int direction, int flags);
+        void sendCommand(String command, Bundle params, ResultReceiver cb);
+
+        boolean isSessionReady();
+        String getPackageName();
+        Object getMediaController();
+    }
+
+    static class MediaControllerImplBase implements MediaControllerImpl {
+        private IMediaSession mBinder;
+        private TransportControls mTransportControls;
+
+        public MediaControllerImplBase(MediaSessionCompat.Token token) {
+            mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
+        }
+
+        @Override
+        public void registerCallback(Callback callback, Handler handler) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback may not be null.");
+            }
+            try {
+                mBinder.asBinder().linkToDeath(callback, 0);
+                mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in registerCallback.", e);
+                callback.onSessionDestroyed();
+            }
+        }
+
+        @Override
+        public void unregisterCallback(Callback callback) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback may not be null.");
+            }
+            try {
+                mBinder.unregisterCallbackListener(
+                        (IMediaControllerCallback) callback.mCallbackObj);
+                mBinder.asBinder().unlinkToDeath(callback, 0);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in unregisterCallback.", e);
+            }
+        }
+
+        @Override
+        public boolean dispatchMediaButtonEvent(KeyEvent event) {
+            if (event == null) {
+                throw new IllegalArgumentException("event may not be null.");
+            }
+            try {
+                mBinder.sendMediaButton(event);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in dispatchMediaButtonEvent.", e);
+            }
+            return false;
+        }
+
+        @Override
+        public TransportControls getTransportControls() {
+            if (mTransportControls == null) {
+                mTransportControls = new TransportControlsBase(mBinder);
+            }
+
+            return mTransportControls;
+        }
+
+        @Override
+        public PlaybackStateCompat getPlaybackState() {
+            try {
+                return mBinder.getPlaybackState();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getPlaybackState.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public MediaMetadataCompat getMetadata() {
+            try {
+                return mBinder.getMetadata();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getMetadata.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public List<QueueItem> getQueue() {
+            try {
+                return mBinder.getQueue();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getQueue.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public void addQueueItem(MediaDescriptionCompat description) {
+            try {
+                long flags = mBinder.getFlags();
+                if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
+                    throw new UnsupportedOperationException(
+                            "This session doesn't support queue management operations");
+                }
+                mBinder.addQueueItem(description);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in addQueueItem.", e);
+            }
+        }
+
+        @Override
+        public void addQueueItem(MediaDescriptionCompat description, int index) {
+            try {
+                long flags = mBinder.getFlags();
+                if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
+                    throw new UnsupportedOperationException(
+                            "This session doesn't support queue management operations");
+                }
+                mBinder.addQueueItemAt(description, index);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in addQueueItemAt.", e);
+            }
+        }
+
+        @Override
+        public void removeQueueItem(MediaDescriptionCompat description) {
+            try {
+                long flags = mBinder.getFlags();
+                if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
+                    throw new UnsupportedOperationException(
+                            "This session doesn't support queue management operations");
+                }
+                mBinder.removeQueueItem(description);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in removeQueueItem.", e);
+            }
+        }
+
+        @Override
+        public CharSequence getQueueTitle() {
+            try {
+                return mBinder.getQueueTitle();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getQueueTitle.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public Bundle getExtras() {
+            try {
+                return mBinder.getExtras();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getExtras.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public int getRatingType() {
+            try {
+                return mBinder.getRatingType();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getRatingType.", e);
+            }
+            return 0;
+        }
+
+        @Override
+        public boolean isCaptioningEnabled() {
+            try {
+                return mBinder.isCaptioningEnabled();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
+            }
+            return false;
+        }
+
+        @Override
+        public int getRepeatMode() {
+            try {
+                return mBinder.getRepeatMode();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getRepeatMode.", e);
+            }
+            return PlaybackStateCompat.REPEAT_MODE_INVALID;
+        }
+
+        @Override
+        public int getShuffleMode() {
+            try {
+                return mBinder.getShuffleMode();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getShuffleMode.", e);
+            }
+            return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
+        }
+
+        @Override
+        public long getFlags() {
+            try {
+                return mBinder.getFlags();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getFlags.", e);
+            }
+            return 0;
+        }
+
+        @Override
+        public PlaybackInfo getPlaybackInfo() {
+            try {
+                ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
+                PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
+                        info.controlType, info.maxVolume, info.currentVolume);
+                return pi;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getPlaybackInfo.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public PendingIntent getSessionActivity() {
+            try {
+                return mBinder.getLaunchPendingIntent();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getSessionActivity.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public void setVolumeTo(int value, int flags) {
+            try {
+                mBinder.setVolumeTo(value, flags, null);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setVolumeTo.", e);
+            }
+        }
+
+        @Override
+        public void adjustVolume(int direction, int flags) {
+            try {
+                mBinder.adjustVolume(direction, flags, null);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in adjustVolume.", e);
+            }
+        }
+
+        @Override
+        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+            try {
+                mBinder.sendCommand(command, params,
+                        new MediaSessionCompat.ResultReceiverWrapper(cb));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in sendCommand.", e);
+            }
+        }
+
+        @Override
+        public boolean isSessionReady() {
+            return true;
+        }
+
+        @Override
+        public String getPackageName() {
+            try {
+                return mBinder.getPackageName();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getPackageName.", e);
+            }
+            return null;
+        }
+
+        @Override
+        public Object getMediaController() {
+            return null;
+        }
+    }
+
+    static class TransportControlsBase extends TransportControls {
+        private IMediaSession mBinder;
+
+        public TransportControlsBase(IMediaSession binder) {
+            mBinder = binder;
+        }
+
+        @Override
+        public void prepare() {
+            try {
+                mBinder.prepare();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepare.", e);
+            }
+        }
+
+        @Override
+        public void prepareFromMediaId(String mediaId, Bundle extras) {
+            try {
+                mBinder.prepareFromMediaId(mediaId, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepareFromMediaId.", e);
+            }
+        }
+
+        @Override
+        public void prepareFromSearch(String query, Bundle extras) {
+            try {
+                mBinder.prepareFromSearch(query, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepareFromSearch.", e);
+            }
+        }
+
+        @Override
+        public void prepareFromUri(Uri uri, Bundle extras) {
+            try {
+                mBinder.prepareFromUri(uri, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepareFromUri.", e);
+            }
+        }
+
+        @Override
+        public void play() {
+            try {
+                mBinder.play();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in play.", e);
+            }
+        }
+
+        @Override
+        public void playFromMediaId(String mediaId, Bundle extras) {
+            try {
+                mBinder.playFromMediaId(mediaId, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in playFromMediaId.", e);
+            }
+        }
+
+        @Override
+        public void playFromSearch(String query, Bundle extras) {
+            try {
+                mBinder.playFromSearch(query, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in playFromSearch.", e);
+            }
+        }
+
+        @Override
+        public void playFromUri(Uri uri, Bundle extras) {
+            try {
+                mBinder.playFromUri(uri, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in playFromUri.", e);
+            }
+        }
+
+        @Override
+        public void skipToQueueItem(long id) {
+            try {
+                mBinder.skipToQueueItem(id);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in skipToQueueItem.", e);
+            }
+        }
+
+        @Override
+        public void pause() {
+            try {
+                mBinder.pause();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in pause.", e);
+            }
+        }
+
+        @Override
+        public void stop() {
+            try {
+                mBinder.stop();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in stop.", e);
+            }
+        }
+
+        @Override
+        public void seekTo(long pos) {
+            try {
+                mBinder.seekTo(pos);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in seekTo.", e);
+            }
+        }
+
+        @Override
+        public void fastForward() {
+            try {
+                mBinder.fastForward();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in fastForward.", e);
+            }
+        }
+
+        @Override
+        public void skipToNext() {
+            try {
+                mBinder.next();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in skipToNext.", e);
+            }
+        }
+
+        @Override
+        public void rewind() {
+            try {
+                mBinder.rewind();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in rewind.", e);
+            }
+        }
+
+        @Override
+        public void skipToPrevious() {
+            try {
+                mBinder.previous();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in skipToPrevious.", e);
+            }
+        }
+
+        @Override
+        public void setRating(RatingCompat rating) {
+            try {
+                mBinder.rate(rating);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setRating.", e);
+            }
+        }
+
+        @Override
+        public void setRating(RatingCompat rating, Bundle extras) {
+            try {
+                mBinder.rateWithExtras(rating, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setRating.", e);
+            }
+        }
+
+        @Override
+        public void setCaptioningEnabled(boolean enabled) {
+            try {
+                mBinder.setCaptioningEnabled(enabled);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setCaptioningEnabled.", e);
+            }
+        }
+
+        @Override
+        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
+            try {
+                mBinder.setRepeatMode(repeatMode);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setRepeatMode.", e);
+            }
+        }
+
+        @Override
+        public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
+            try {
+                mBinder.setShuffleMode(shuffleMode);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setShuffleMode.", e);
+            }
+        }
+
+        @Override
+        public void sendCustomAction(CustomAction customAction, Bundle args) {
+            sendCustomAction(customAction.getAction(), args);
+        }
+
+        @Override
+        public void sendCustomAction(String action, Bundle args) {
+            validateCustomAction(action, args);
+            try {
+                mBinder.sendCustomAction(action, args);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in sendCustomAction.", e);
+            }
+        }
+    }
+
+    @RequiresApi(21)
+    static class MediaControllerImplApi21 implements MediaControllerImpl {
+        protected final Object mControllerObj;
+
+        private final List<Callback> mPendingCallbacks = new ArrayList<>();
+
+        // Extra binder is used for applying the framework change of new APIs and bug fixes
+        // after API 21.
+        private IMediaSession mExtraBinder;
+        private HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>();
+
+        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
+            mControllerObj = MediaControllerCompatApi21.fromToken(context,
+                    session.getSessionToken().getToken());
+            mExtraBinder = session.getSessionToken().getExtraBinder();
+            if (mExtraBinder == null) {
+                requestExtraBinder();
+            }
+        }
+
+        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
+                throws RemoteException {
+            mControllerObj = MediaControllerCompatApi21.fromToken(context,
+                    sessionToken.getToken());
+            if (mControllerObj == null) throw new RemoteException();
+            mExtraBinder = sessionToken.getExtraBinder();
+            if (mExtraBinder == null) {
+                requestExtraBinder();
+            }
+        }
+
+        @Override
+        public final void registerCallback(Callback callback, Handler handler) {
+            MediaControllerCompatApi21.registerCallback(
+                    mControllerObj, callback.mCallbackObj, handler);
+            if (mExtraBinder != null) {
+                ExtraCallback extraCallback = new ExtraCallback(callback);
+                mCallbackMap.put(callback, extraCallback);
+                callback.mHasExtraCallback = true;
+                try {
+                    mExtraBinder.registerCallbackListener(extraCallback);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Dead object in registerCallback.", e);
+                }
+            } else {
+                synchronized (mPendingCallbacks) {
+                    callback.mHasExtraCallback = false;
+                    mPendingCallbacks.add(callback);
+                }
+            }
+        }
+
+        @Override
+        public final void unregisterCallback(Callback callback) {
+            MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
+            if (mExtraBinder != null) {
+                try {
+                    ExtraCallback extraCallback = mCallbackMap.remove(callback);
+                    if (extraCallback != null) {
+                        callback.mHasExtraCallback = false;
+                        mExtraBinder.unregisterCallbackListener(extraCallback);
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Dead object in unregisterCallback.", e);
+                }
+            } else {
+                synchronized (mPendingCallbacks) {
+                    mPendingCallbacks.remove(callback);
+                }
+            }
+        }
+
+        @Override
+        public boolean dispatchMediaButtonEvent(KeyEvent event) {
+            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
+        }
+
+        @Override
+        public TransportControls getTransportControls() {
+            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
+            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
+        }
+
+        @Override
+        public PlaybackStateCompat getPlaybackState() {
+            if (mExtraBinder != null) {
+                try {
+                    return mExtraBinder.getPlaybackState();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Dead object in getPlaybackState.", e);
+                }
+            }
+            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
+            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
+        }
+
+        @Override
+        public MediaMetadataCompat getMetadata() {
+            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
+            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
+        }
+
+        @Override
+        public List<QueueItem> getQueue() {
+            List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
+            return queueObjs != null ? QueueItem.fromQueueItemList(queueObjs) : null;
+        }
+
+        @Override
+        public void addQueueItem(MediaDescriptionCompat description) {
+            long flags = getFlags();
+            if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
+                throw new UnsupportedOperationException(
+                        "This session doesn't support queue management operations");
+            }
+            Bundle params = new Bundle();
+            params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
+            sendCommand(COMMAND_ADD_QUEUE_ITEM, params, null);
+        }
+
+        @Override
+        public void addQueueItem(MediaDescriptionCompat description, int index) {
+            long flags = getFlags();
+            if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
+                throw new UnsupportedOperationException(
+                        "This session doesn't support queue management operations");
+            }
+            Bundle params = new Bundle();
+            params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
+            params.putInt(COMMAND_ARGUMENT_INDEX, index);
+            sendCommand(COMMAND_ADD_QUEUE_ITEM_AT, params, null);
+        }
+
+        @Override
+        public void removeQueueItem(MediaDescriptionCompat description) {
+            long flags = getFlags();
+            if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
+                throw new UnsupportedOperationException(
+                        "This session doesn't support queue management operations");
+            }
+            Bundle params = new Bundle();
+            params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
+            sendCommand(COMMAND_REMOVE_QUEUE_ITEM, params, null);
+        }
+
+        @Override
+        public CharSequence getQueueTitle() {
+            return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
+        }
+
+        @Override
+        public Bundle getExtras() {
+            return MediaControllerCompatApi21.getExtras(mControllerObj);
+        }
+
+        @Override
+        public int getRatingType() {
+            if (android.os.Build.VERSION.SDK_INT < 22 && mExtraBinder != null) {
+                try {
+                    return mExtraBinder.getRatingType();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Dead object in getRatingType.", e);
+                }
+            }
+            return MediaControllerCompatApi21.getRatingType(mControllerObj);
+        }
+
+        @Override
+        public boolean isCaptioningEnabled() {
+            if (mExtraBinder != null) {
+                try {
+                    return mExtraBinder.isCaptioningEnabled();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public int getRepeatMode() {
+            if (mExtraBinder != null) {
+                try {
+                    return mExtraBinder.getRepeatMode();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Dead object in getRepeatMode.", e);
+                }
+            }
+            return PlaybackStateCompat.REPEAT_MODE_INVALID;
+        }
+
+        @Override
+        public int getShuffleMode() {
+            if (mExtraBinder != null) {
+                try {
+                    return mExtraBinder.getShuffleMode();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Dead object in getShuffleMode.", e);
+                }
+            }
+            return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
+        }
+
+        @Override
+        public long getFlags() {
+            return MediaControllerCompatApi21.getFlags(mControllerObj);
+        }
+
+        @Override
+        public PlaybackInfo getPlaybackInfo() {
+            Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
+            return volumeInfoObj != null ? new PlaybackInfo(
+                    MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
+                    MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
+                    MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
+                    MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
+                    MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
+        }
+
+        @Override
+        public PendingIntent getSessionActivity() {
+            return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
+        }
+
+        @Override
+        public void setVolumeTo(int value, int flags) {
+            MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
+        }
+
+        @Override
+        public void adjustVolume(int direction, int flags) {
+            MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
+        }
+
+        @Override
+        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+            MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
+        }
+
+        @Override
+        public boolean isSessionReady() {
+            return mExtraBinder != null;
+        }
+
+        @Override
+        public String getPackageName() {
+            return MediaControllerCompatApi21.getPackageName(mControllerObj);
+        }
+
+        @Override
+        public Object getMediaController() {
+            return mControllerObj;
+        }
+
+        private void requestExtraBinder() {
+            sendCommand(COMMAND_GET_EXTRA_BINDER, null,
+                    new ExtraBinderRequestResultReceiver(this, new Handler()));
+        }
+
+        private void processPendingCallbacks() {
+            if (mExtraBinder == null) {
+                return;
+            }
+            synchronized (mPendingCallbacks) {
+                for (Callback callback : mPendingCallbacks) {
+                    ExtraCallback extraCallback = new ExtraCallback(callback);
+                    mCallbackMap.put(callback, extraCallback);
+                    callback.mHasExtraCallback = true;
+                    try {
+                        mExtraBinder.registerCallbackListener(extraCallback);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Dead object in registerCallback.", e);
+                        break;
+                    }
+                    callback.onSessionReady();
+                }
+                mPendingCallbacks.clear();
+            }
+        }
+
+        private static class ExtraBinderRequestResultReceiver extends ResultReceiver {
+            private WeakReference<MediaControllerImplApi21> mMediaControllerImpl;
+
+            public ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl,
+                    Handler handler) {
+                super(handler);
+                mMediaControllerImpl = new WeakReference<>(mediaControllerImpl);
+            }
+
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                MediaControllerImplApi21 mediaControllerImpl = mMediaControllerImpl.get();
+                if (mediaControllerImpl == null || resultData == null) {
+                    return;
+                }
+                mediaControllerImpl.mExtraBinder = IMediaSession.Stub.asInterface(
+                        BundleCompat.getBinder(resultData, MediaSessionCompat.EXTRA_BINDER));
+                mediaControllerImpl.processPendingCallbacks();
+            }
+        }
+
+        private static class ExtraCallback extends Callback.StubCompat {
+            ExtraCallback(Callback callback) {
+                super(callback);
+            }
+
+            @Override
+            public void onSessionDestroyed() throws RemoteException {
+                // Will not be called.
+                throw new AssertionError();
+            }
+
+            @Override
+            public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
+                // Will not be called.
+                throw new AssertionError();
+            }
+
+            @Override
+            public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
+                // Will not be called.
+                throw new AssertionError();
+            }
+
+            @Override
+            public void onQueueTitleChanged(CharSequence title) throws RemoteException {
+                // Will not be called.
+                throw new AssertionError();
+            }
+
+            @Override
+            public void onExtrasChanged(Bundle extras) throws RemoteException {
+                // Will not be called.
+                throw new AssertionError();
+            }
+
+            @Override
+            public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
+                // Will not be called.
+                throw new AssertionError();
+            }
+        }
+    }
+
+    static class TransportControlsApi21 extends TransportControls {
+        protected final Object mControlsObj;
+
+        public TransportControlsApi21(Object controlsObj) {
+            mControlsObj = controlsObj;
+        }
+
+        @Override
+        public void prepare() {
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
+        }
+
+        @Override
+        public void prepareFromMediaId(String mediaId, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
+            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
+        }
+
+        @Override
+        public void prepareFromSearch(String query, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
+            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
+        }
+
+        @Override
+        public void prepareFromUri(Uri uri, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
+            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
+        }
+
+        @Override
+        public void play() {
+            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
+        }
+
+        @Override
+        public void pause() {
+            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
+        }
+
+        @Override
+        public void stop() {
+            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
+        }
+
+        @Override
+        public void seekTo(long pos) {
+            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
+        }
+
+        @Override
+        public void fastForward() {
+            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
+        }
+
+        @Override
+        public void rewind() {
+            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
+        }
+
+        @Override
+        public void skipToNext() {
+            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
+        }
+
+        @Override
+        public void skipToPrevious() {
+            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
+        }
+
+        @Override
+        public void setRating(RatingCompat rating) {
+            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
+                    rating != null ? rating.getRating() : null);
+        }
+
+        @Override
+        public void setRating(RatingCompat rating, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_RATING, rating);
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_SET_RATING, bundle);
+        }
+
+        @Override
+        public void setCaptioningEnabled(boolean enabled) {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_CAPTIONING_ENABLED, enabled);
+            sendCustomAction(MediaSessionCompat.ACTION_SET_CAPTIONING_ENABLED, bundle);
+        }
+
+        @Override
+        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
+            Bundle bundle = new Bundle();
+            bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_REPEAT_MODE, repeatMode);
+            sendCustomAction(MediaSessionCompat.ACTION_SET_REPEAT_MODE, bundle);
+        }
+
+        @Override
+        public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
+            Bundle bundle = new Bundle();
+            bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE, shuffleMode);
+            sendCustomAction(MediaSessionCompat.ACTION_SET_SHUFFLE_MODE, bundle);
+        }
+
+        @Override
+        public void playFromMediaId(String mediaId, Bundle extras) {
+            MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
+                    extras);
+        }
+
+        @Override
+        public void playFromSearch(String query, Bundle extras) {
+            MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
+                    extras);
+        }
+
+        @Override
+        public void playFromUri(Uri uri, Bundle extras) {
+            if (uri == null || Uri.EMPTY.equals(uri)) {
+                throw new IllegalArgumentException(
+                        "You must specify a non-empty Uri for playFromUri.");
+            }
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle);
+        }
+
+        @Override
+        public void skipToQueueItem(long id) {
+            MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
+        }
+
+        @Override
+        public void sendCustomAction(CustomAction customAction, Bundle args) {
+            validateCustomAction(customAction.getAction(), args);
+            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
+                    customAction.getAction(), args);
+        }
+
+        @Override
+        public void sendCustomAction(String action, Bundle args) {
+            validateCustomAction(action, args);
+            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
+                    args);
+        }
+    }
+
+    @RequiresApi(23)
+    static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
+
+        public MediaControllerImplApi23(Context context, MediaSessionCompat session) {
+            super(context, session);
+        }
+
+        public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
+                throws RemoteException {
+            super(context, sessionToken);
+        }
+
+        @Override
+        public TransportControls getTransportControls() {
+            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
+            return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
+        }
+    }
+
+    @RequiresApi(23)
+    static class TransportControlsApi23 extends TransportControlsApi21 {
+
+        public TransportControlsApi23(Object controlsObj) {
+            super(controlsObj);
+        }
+
+        @Override
+        public void playFromUri(Uri uri, Bundle extras) {
+            MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
+                    extras);
+        }
+    }
+
+    @RequiresApi(24)
+    static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
+
+        public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
+            super(context, session);
+        }
+
+        public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
+                throws RemoteException {
+            super(context, sessionToken);
+        }
+
+        @Override
+        public TransportControls getTransportControls() {
+            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
+            return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
+        }
+    }
+
+    @RequiresApi(24)
+    static class TransportControlsApi24 extends TransportControlsApi23 {
+
+        public TransportControlsApi24(Object controlsObj) {
+            super(controlsObj);
+        }
+
+        @Override
+        public void prepare() {
+            MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
+        }
+
+        @Override
+        public void prepareFromMediaId(String mediaId, Bundle extras) {
+            MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
+                    mControlsObj, mediaId, extras);
+        }
+
+        @Override
+        public void prepareFromSearch(String query, Bundle extras) {
+            MediaControllerCompatApi24.TransportControls.prepareFromSearch(
+                    mControlsObj, query, extras);
+        }
+
+        @Override
+        public void prepareFromUri(Uri uri, Bundle extras) {
+            MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
+        }
+    }
+}
diff --git a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index fb939c0..1d78fc8 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -60,7 +60,6 @@
 import androidx.core.app.BundleCompat;
 import androidx.media.VolumeProviderCompat;
 import androidx.media.session.MediaButtonReceiver;
-import androidx.media.session.MediaControllerCompat;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
index c2a9741..e6420ea 100644
--- a/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -30,7 +30,6 @@
 import androidx.annotation.LongDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.media.session.MediaControllerCompat;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/media/src/main/java/androidx/media/MediaBrowserCompat.java b/media/src/main/java/androidx/media/MediaBrowserCompat.java
deleted file mode 100644
index 05123dc..0000000
--- a/media/src/main/java/androidx/media/MediaBrowserCompat.java
+++ /dev/null
@@ -1,2289 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_ADD_SUBSCRIPTION;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_CONNECT;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_DISCONNECT;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_GET_MEDIA_ITEM;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_REGISTER_CALLBACK_MESSENGER;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_REMOVE_SUBSCRIPTION;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_SEARCH;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_SEND_CUSTOM_ACTION;
-import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER;
-import static androidx.media.MediaBrowserProtocol.CLIENT_VERSION_CURRENT;
-import static androidx.media.MediaBrowserProtocol.DATA_CALLBACK_TOKEN;
-import static androidx.media.MediaBrowserProtocol.DATA_CUSTOM_ACTION;
-import static androidx.media.MediaBrowserProtocol.DATA_CUSTOM_ACTION_EXTRAS;
-import static androidx.media.MediaBrowserProtocol.DATA_MEDIA_ITEM_ID;
-import static androidx.media.MediaBrowserProtocol.DATA_MEDIA_ITEM_LIST;
-import static androidx.media.MediaBrowserProtocol.DATA_MEDIA_SESSION_TOKEN;
-import static androidx.media.MediaBrowserProtocol.DATA_OPTIONS;
-import static androidx.media.MediaBrowserProtocol.DATA_PACKAGE_NAME;
-import static androidx.media.MediaBrowserProtocol.DATA_RESULT_RECEIVER;
-import static androidx.media.MediaBrowserProtocol.DATA_ROOT_HINTS;
-import static androidx.media.MediaBrowserProtocol.DATA_SEARCH_EXTRAS;
-import static androidx.media.MediaBrowserProtocol.DATA_SEARCH_QUERY;
-import static androidx.media.MediaBrowserProtocol.EXTRA_CLIENT_VERSION;
-import static androidx.media.MediaBrowserProtocol.EXTRA_MESSENGER_BINDER;
-import static androidx.media.MediaBrowserProtocol.EXTRA_SERVICE_VERSION;
-import static androidx.media.MediaBrowserProtocol.EXTRA_SESSION_BINDER;
-import static androidx.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT;
-import static androidx.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT_FAILED;
-import static androidx.media.MediaBrowserProtocol.SERVICE_MSG_ON_LOAD_CHILDREN;
-import static androidx.media.MediaBrowserProtocol.SERVICE_VERSION_2;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.BadParcelableException;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.IMediaSession;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.collection.ArrayMap;
-import androidx.core.app.BundleCompat;
-import androidx.media.session.MediaControllerCompat.TransportControls;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Browses media content offered by a {@link MediaBrowserServiceCompat}.
- * <p>
- * This object is not thread-safe. All calls should happen on the thread on which the browser
- * was constructed.
- * </p><p>
- * All callback methods will be called from the thread on which the browser was constructed.
- * </p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For information about building your media application, read the
- * <a href="{@docRoot}guide/topics/media-apps/index.html">Media Apps</a> developer guide.</p>
- * </div>
- */
-public final class MediaBrowserCompat {
-    static final String TAG = "MediaBrowserCompat";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    /**
-     * Used as an int extra field to denote the page number to subscribe.
-     * The value of {@code EXTRA_PAGE} should be greater than or equal to 1.
-     *
-     * @see android.service.media.MediaBrowserService.BrowserRoot
-     * @see #EXTRA_PAGE_SIZE
-     */
-    public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
-
-    /**
-     * Used as an int extra field to denote the number of media items in a page.
-     * The value of {@code EXTRA_PAGE_SIZE} should be greater than or equal to 1.
-     *
-     * @see android.service.media.MediaBrowserService.BrowserRoot
-     * @see #EXTRA_PAGE
-     */
-    public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
-
-    /**
-     * Used as a string extra field to denote the target {@link MediaItem}.
-     *
-     * @see #CUSTOM_ACTION_DOWNLOAD
-     * @see #CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE
-     */
-    public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
-
-    /**
-     * Used as a float extra field to denote the current progress during download. The value of this
-     * field must be a float number within [0.0, 1.0].
-     *
-     * @see #CUSTOM_ACTION_DOWNLOAD
-     * @see CustomActionCallback#onProgressUpdate
-     */
-    public static final String EXTRA_DOWNLOAD_PROGRESS =
-            "android.media.browse.extra.DOWNLOAD_PROGRESS";
-
-    /**
-     * Predefined custom action to ask the connected service to download a specific
-     * {@link MediaItem} for offline playback. The id of the media item must be passed in an extra
-     * bundle. The download progress might be delivered to the browser via
-     * {@link CustomActionCallback#onProgressUpdate}.
-     *
-     * @see #EXTRA_MEDIA_ID
-     * @see #EXTRA_DOWNLOAD_PROGRESS
-     * @see #CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE
-     */
-    public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
-
-    /**
-     * Predefined custom action to ask the connected service to remove the downloaded file of
-     * {@link MediaItem} by the {@link #CUSTOM_ACTION_DOWNLOAD download} action. The id of the
-     * media item must be passed in an extra bundle.
-     *
-     * @see #EXTRA_MEDIA_ID
-     * @see #CUSTOM_ACTION_DOWNLOAD
-     */
-    public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE =
-            "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
-
-    private final MediaBrowserImpl mImpl;
-
-    /**
-     * Creates a media browser for the specified media browse service.
-     *
-     * @param context The context.
-     * @param serviceComponent The component name of the media browse service.
-     * @param callback The connection callback.
-     * @param rootHints An optional bundle of service-specific arguments to send
-     * to the media browse service when connecting and retrieving the root id
-     * for browsing, or null if none. The contents of this bundle may affect
-     * the information returned when browsing.
-     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_RECENT
-     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_OFFLINE
-     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_SUGGESTED
-     */
-    public MediaBrowserCompat(Context context, ComponentName serviceComponent,
-            ConnectionCallback callback, Bundle rootHints) {
-        // To workaround an issue of {@link #unsubscribe(String, SubscriptionCallback)} on API 24
-        // and 25 devices, use the support library version of implementation on those devices.
-        if (Build.VERSION.SDK_INT >= 26) {
-            mImpl = new MediaBrowserImplApi26(context, serviceComponent, callback, rootHints);
-        } else if (Build.VERSION.SDK_INT >= 23) {
-            mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
-        } else if (Build.VERSION.SDK_INT >= 21) {
-            mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
-        } else {
-            mImpl = new MediaBrowserImplBase(context, serviceComponent, callback, rootHints);
-        }
-    }
-
-    /**
-     * Connects to the media browse service.
-     * <p>
-     * The connection callback specified in the constructor will be invoked
-     * when the connection completes or fails.
-     * </p>
-     */
-    public void connect() {
-        mImpl.connect();
-    }
-
-    /**
-     * Disconnects from the media browse service.
-     * After this, no more callbacks will be received.
-     */
-    public void disconnect() {
-        mImpl.disconnect();
-    }
-
-    /**
-     * Returns whether the browser is connected to the service.
-     */
-    public boolean isConnected() {
-        return mImpl.isConnected();
-    }
-
-    /**
-     * Gets the service component that the media browser is connected to.
-     */
-    public @NonNull
-    ComponentName getServiceComponent() {
-        return mImpl.getServiceComponent();
-    }
-
-    /**
-     * Gets the root id.
-     * <p>
-     * Note that the root id may become invalid or change when when the
-     * browser is disconnected.
-     * </p>
-     *
-     * @throws IllegalStateException if not connected.
-     */
-    public @NonNull String getRoot() {
-        return mImpl.getRoot();
-    }
-
-    /**
-     * Gets any extras for the media service.
-     *
-     * @throws IllegalStateException if not connected.
-     */
-    public @Nullable
-    Bundle getExtras() {
-        return mImpl.getExtras();
-    }
-
-    /**
-     * Gets the media session token associated with the media browser.
-     * <p>
-     * Note that the session token may become invalid or change when when the
-     * browser is disconnected.
-     * </p>
-     *
-     * @return The session token for the browser, never null.
-     *
-     * @throws IllegalStateException if not connected.
-     */
-    public @NonNull MediaSessionCompat.Token getSessionToken() {
-        return mImpl.getSessionToken();
-    }
-
-    /**
-     * Queries for information about the media items that are contained within
-     * the specified id and subscribes to receive updates when they change.
-     * <p>
-     * The list of subscriptions is maintained even when not connected and is
-     * restored after the reconnection. It is ok to subscribe while not connected
-     * but the results will not be returned until the connection completes.
-     * </p>
-     * <p>
-     * If the id is already subscribed with a different callback then the new
-     * callback will replace the previous one and the child data will be
-     * reloaded.
-     * </p>
-     *
-     * @param parentId The id of the parent media item whose list of children
-     *            will be subscribed.
-     * @param callback The callback to receive the list of children.
-     */
-    public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
-        // Check arguments.
-        if (TextUtils.isEmpty(parentId)) {
-            throw new IllegalArgumentException("parentId is empty");
-        }
-        if (callback == null) {
-            throw new IllegalArgumentException("callback is null");
-        }
-        mImpl.subscribe(parentId, null, callback);
-    }
-
-    /**
-     * Queries with service-specific arguments for information about the media items
-     * that are contained within the specified id and subscribes to receive updates
-     * when they change.
-     * <p>
-     * The list of subscriptions is maintained even when not connected and is
-     * restored after the reconnection. It is ok to subscribe while not connected
-     * but the results will not be returned until the connection completes.
-     * </p>
-     * <p>
-     * If the id is already subscribed with a different callback then the new
-     * callback will replace the previous one and the child data will be
-     * reloaded.
-     * </p>
-     *
-     * @param parentId The id of the parent media item whose list of children
-     *            will be subscribed.
-     * @param options A bundle of service-specific arguments to send to the media
-     *            browse service. The contents of this bundle may affect the
-     *            information returned when browsing.
-     * @param callback The callback to receive the list of children.
-     */
-    public void subscribe(@NonNull String parentId, @NonNull Bundle options,
-            @NonNull SubscriptionCallback callback) {
-        // Check arguments.
-        if (TextUtils.isEmpty(parentId)) {
-            throw new IllegalArgumentException("parentId is empty");
-        }
-        if (callback == null) {
-            throw new IllegalArgumentException("callback is null");
-        }
-        if (options == null) {
-            throw new IllegalArgumentException("options are null");
-        }
-        mImpl.subscribe(parentId, options, callback);
-    }
-
-    /**
-     * Unsubscribes for changes to the children of the specified media id.
-     * <p>
-     * The query callback will no longer be invoked for results associated with
-     * this id once this method returns.
-     * </p>
-     *
-     * @param parentId The id of the parent media item whose list of children
-     *            will be unsubscribed.
-     */
-    public void unsubscribe(@NonNull String parentId) {
-        // Check arguments.
-        if (TextUtils.isEmpty(parentId)) {
-            throw new IllegalArgumentException("parentId is empty");
-        }
-        mImpl.unsubscribe(parentId, null);
-    }
-
-    /**
-     * Unsubscribes for changes to the children of the specified media id.
-     * <p>
-     * The query callback will no longer be invoked for results associated with
-     * this id once this method returns.
-     * </p>
-     *
-     * @param parentId The id of the parent media item whose list of children
-     *            will be unsubscribed.
-     * @param callback A callback sent to the media browse service to subscribe.
-     */
-    public void unsubscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
-        // Check arguments.
-        if (TextUtils.isEmpty(parentId)) {
-            throw new IllegalArgumentException("parentId is empty");
-        }
-        if (callback == null) {
-            throw new IllegalArgumentException("callback is null");
-        }
-        mImpl.unsubscribe(parentId, callback);
-    }
-
-    /**
-     * Retrieves a specific {@link MediaItem} from the connected service. Not
-     * all services may support this, so falling back to subscribing to the
-     * parent's id should be used when unavailable.
-     *
-     * @param mediaId The id of the item to retrieve.
-     * @param cb The callback to receive the result on.
-     */
-    public void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb) {
-        mImpl.getItem(mediaId, cb);
-    }
-
-    /**
-     * Searches {@link MediaItem media items} from the connected service. Not all services may
-     * support this, and {@link SearchCallback#onError} will be called if not implemented.
-     *
-     * @param query The search query that contains keywords separated by space. Should not be an
-     *            empty string.
-     * @param extras The bundle of service-specific arguments to send to the media browser service.
-     *            The contents of this bundle may affect the search result.
-     * @param callback The callback to receive the search result. Must be non-null.
-     * @throws IllegalStateException if the browser is not connected to the media browser service.
-     */
-    public void search(@NonNull final String query, final Bundle extras,
-            @NonNull SearchCallback callback) {
-        if (TextUtils.isEmpty(query)) {
-            throw new IllegalArgumentException("query cannot be empty");
-        }
-        if (callback == null) {
-            throw new IllegalArgumentException("callback cannot be null");
-        }
-        mImpl.search(query, extras, callback);
-    }
-
-    /**
-     * Sends a custom action to the connected service. If the service doesn't support the given
-     * action, {@link CustomActionCallback#onError} will be called.
-     *
-     * @param action The custom action that will be sent to the connected service. Should not be an
-     *            empty string.
-     * @param extras The bundle of service-specific arguments to send to the media browser service.
-     * @param callback The callback to receive the result of the custom action.
-     * @see #CUSTOM_ACTION_DOWNLOAD
-     * @see #CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE
-     */
-    public void sendCustomAction(@NonNull String action, Bundle extras,
-            @Nullable CustomActionCallback callback) {
-        if (TextUtils.isEmpty(action)) {
-            throw new IllegalArgumentException("action cannot be empty");
-        }
-        mImpl.sendCustomAction(action, extras, callback);
-    }
-
-    /**
-     * A class with information on a single media item for use in browsing/searching media.
-     * MediaItems are application dependent so we cannot guarantee that they contain the
-     * right values.
-     */
-    public static class MediaItem implements Parcelable {
-        private final int mFlags;
-        private final MediaDescriptionCompat mDescription;
-
-        /** @hide */
-        @RestrictTo(LIBRARY_GROUP)
-        @Retention(RetentionPolicy.SOURCE)
-        @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
-        public @interface Flags { }
-
-        /**
-         * Flag: Indicates that the item has children of its own.
-         */
-        public static final int FLAG_BROWSABLE = 1 << 0;
-
-        /**
-         * Flag: Indicates that the item is playable.
-         * <p>
-         * The id of this item may be passed to
-         * {@link TransportControls#playFromMediaId(String, Bundle)}
-         * to start playing it.
-         * </p>
-         */
-        public static final int FLAG_PLAYABLE = 1 << 1;
-
-        /**
-         * Creates an instance from a framework {@link android.media.browse.MediaBrowser.MediaItem}
-         * object.
-         * <p>
-         * This method is only supported on API 21+. On API 20 and below, it returns null.
-         * </p>
-         *
-         * @param itemObj A {@link android.media.browse.MediaBrowser.MediaItem} object.
-         * @return An equivalent {@link MediaItem} object, or null if none.
-         */
-        public static MediaItem fromMediaItem(Object itemObj) {
-            if (itemObj == null || Build.VERSION.SDK_INT < 21) {
-                return null;
-            }
-            int flags = MediaBrowserCompatApi21.MediaItem.getFlags(itemObj);
-            MediaDescriptionCompat description =
-                    MediaDescriptionCompat.fromMediaDescription(
-                            MediaBrowserCompatApi21.MediaItem.getDescription(itemObj));
-            return new MediaItem(description, flags);
-        }
-
-        /**
-         * Creates a list of {@link MediaItem} objects from a framework
-         * {@link android.media.browse.MediaBrowser.MediaItem} object list.
-         * <p>
-         * This method is only supported on API 21+. On API 20 and below, it returns null.
-         * </p>
-         *
-         * @param itemList A list of {@link android.media.browse.MediaBrowser.MediaItem} objects.
-         * @return An equivalent list of {@link MediaItem} objects, or null if none.
-         */
-        public static List<MediaItem> fromMediaItemList(List<?> itemList) {
-            if (itemList == null || Build.VERSION.SDK_INT < 21) {
-                return null;
-            }
-            List<MediaItem> items = new ArrayList<>(itemList.size());
-            for (Object itemObj : itemList) {
-                items.add(fromMediaItem(itemObj));
-            }
-            return items;
-        }
-
-        /**
-         * Create a new MediaItem for use in browsing media.
-         * @param description The description of the media, which must include a
-         *            media id.
-         * @param flags The flags for this item.
-         */
-        public MediaItem(@NonNull MediaDescriptionCompat description, @Flags int flags) {
-            if (description == null) {
-                throw new IllegalArgumentException("description cannot be null");
-            }
-            if (TextUtils.isEmpty(description.getMediaId())) {
-                throw new IllegalArgumentException("description must have a non-empty media id");
-            }
-            mFlags = flags;
-            mDescription = description;
-        }
-
-        /**
-         * Private constructor.
-         */
-        MediaItem(Parcel in) {
-            mFlags = in.readInt();
-            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(mFlags);
-            mDescription.writeToParcel(out, flags);
-        }
-
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder("MediaItem{");
-            sb.append("mFlags=").append(mFlags);
-            sb.append(", mDescription=").append(mDescription);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        public static final Parcelable.Creator<MediaItem> CREATOR =
-                new Parcelable.Creator<MediaItem>() {
-                    @Override
-                    public MediaItem createFromParcel(Parcel in) {
-                        return new MediaItem(in);
-                    }
-
-                    @Override
-                    public MediaItem[] newArray(int size) {
-                        return new MediaItem[size];
-                    }
-                };
-
-        /**
-         * Gets the flags of the item.
-         */
-        public @Flags int getFlags() {
-            return mFlags;
-        }
-
-        /**
-         * Returns whether this item is browsable.
-         * @see #FLAG_BROWSABLE
-         */
-        public boolean isBrowsable() {
-            return (mFlags & FLAG_BROWSABLE) != 0;
-        }
-
-        /**
-         * Returns whether this item is playable.
-         * @see #FLAG_PLAYABLE
-         */
-        public boolean isPlayable() {
-            return (mFlags & FLAG_PLAYABLE) != 0;
-        }
-
-        /**
-         * Returns the description of the media.
-         */
-        public @NonNull MediaDescriptionCompat getDescription() {
-            return mDescription;
-        }
-
-        /**
-         * Returns the media id in the {@link MediaDescriptionCompat} for this item.
-         * @see MediaMetadataCompat#METADATA_KEY_MEDIA_ID
-         */
-        public @Nullable String getMediaId() {
-            return mDescription.getMediaId();
-        }
-    }
-
-    /**
-     * Callbacks for connection related events.
-     */
-    public static class ConnectionCallback {
-        final Object mConnectionCallbackObj;
-        ConnectionCallbackInternal mConnectionCallbackInternal;
-
-        public ConnectionCallback() {
-            if (Build.VERSION.SDK_INT >= 21) {
-                mConnectionCallbackObj =
-                        MediaBrowserCompatApi21.createConnectionCallback(new StubApi21());
-            } else {
-                mConnectionCallbackObj = null;
-            }
-        }
-
-        /**
-         * Invoked after {@link MediaBrowserCompat#connect()} when the request has successfully
-         * completed.
-         */
-        public void onConnected() {
-        }
-
-        /**
-         * Invoked when the client is disconnected from the media browser.
-         */
-        public void onConnectionSuspended() {
-        }
-
-        /**
-         * Invoked when the connection to the media browser failed.
-         */
-        public void onConnectionFailed() {
-        }
-
-        void setInternalConnectionCallback(ConnectionCallbackInternal connectionCallbackInternal) {
-            mConnectionCallbackInternal = connectionCallbackInternal;
-        }
-
-        interface ConnectionCallbackInternal {
-            void onConnected();
-            void onConnectionSuspended();
-            void onConnectionFailed();
-        }
-
-        private class StubApi21 implements MediaBrowserCompatApi21.ConnectionCallback {
-            StubApi21() {
-            }
-
-            @Override
-            public void onConnected() {
-                if (mConnectionCallbackInternal != null) {
-                    mConnectionCallbackInternal.onConnected();
-                }
-                ConnectionCallback.this.onConnected();
-            }
-
-            @Override
-            public void onConnectionSuspended() {
-                if (mConnectionCallbackInternal != null) {
-                    mConnectionCallbackInternal.onConnectionSuspended();
-                }
-                ConnectionCallback.this.onConnectionSuspended();
-            }
-
-            @Override
-            public void onConnectionFailed() {
-                if (mConnectionCallbackInternal != null) {
-                    mConnectionCallbackInternal.onConnectionFailed();
-                }
-                ConnectionCallback.this.onConnectionFailed();
-            }
-        }
-    }
-
-    /**
-     * Callbacks for subscription related events.
-     */
-    public static abstract class SubscriptionCallback {
-        private final Object mSubscriptionCallbackObj;
-        private final IBinder mToken;
-        WeakReference<Subscription> mSubscriptionRef;
-
-        public SubscriptionCallback() {
-            mToken = new Binder();
-            if (Build.VERSION.SDK_INT >= 26) {
-                mSubscriptionCallbackObj =
-                        MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26());
-            } else if (Build.VERSION.SDK_INT >= 21) {
-                mSubscriptionCallbackObj =
-                        MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
-            } else {
-                mSubscriptionCallbackObj = null;
-            }
-        }
-
-        /**
-         * Called when the list of children is loaded or updated.
-         *
-         * @param parentId The media id of the parent media item.
-         * @param children The children which were loaded.
-         */
-        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children) {
-        }
-
-        /**
-         * Called when the list of children is loaded or updated.
-         *
-         * @param parentId The media id of the parent media item.
-         * @param children The children which were loaded.
-         * @param options A bundle of service-specific arguments to send to the media
-         *            browse service. The contents of this bundle may affect the
-         *            information returned when browsing.
-         */
-        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children,
-                @NonNull Bundle options) {
-        }
-
-        /**
-         * Called when the id doesn't exist or other errors in subscribing.
-         * <p>
-         * If this is called, the subscription remains until {@link MediaBrowserCompat#unsubscribe}
-         * called, because some errors may heal themselves.
-         * </p>
-         *
-         * @param parentId The media id of the parent media item whose children could not be loaded.
-         */
-        public void onError(@NonNull String parentId) {
-        }
-
-        /**
-         * Called when the id doesn't exist or other errors in subscribing.
-         * <p>
-         * If this is called, the subscription remains until {@link MediaBrowserCompat#unsubscribe}
-         * called, because some errors may heal themselves.
-         * </p>
-         *
-         * @param parentId The media id of the parent media item whose children could
-         *            not be loaded.
-         * @param options A bundle of service-specific arguments sent to the media
-         *            browse service.
-         */
-        public void onError(@NonNull String parentId, @NonNull Bundle options) {
-        }
-
-        private void setSubscription(Subscription subscription) {
-            mSubscriptionRef = new WeakReference<>(subscription);
-        }
-
-        private class StubApi21 implements MediaBrowserCompatApi21.SubscriptionCallback {
-            StubApi21() {
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId, List<?> children) {
-                Subscription sub = mSubscriptionRef == null ? null : mSubscriptionRef.get();
-                if (sub == null) {
-                    SubscriptionCallback.this.onChildrenLoaded(
-                            parentId, MediaItem.fromMediaItemList(children));
-                } else {
-                    List<MediaBrowserCompat.MediaItem> itemList =
-                            MediaItem.fromMediaItemList(children);
-                    final List<SubscriptionCallback> callbacks = sub.getCallbacks();
-                    final List<Bundle> optionsList = sub.getOptionsList();
-                    for (int i = 0; i < callbacks.size(); ++i) {
-                        Bundle options = optionsList.get(i);
-                        if (options == null) {
-                            SubscriptionCallback.this.onChildrenLoaded(parentId, itemList);
-                        } else {
-                            SubscriptionCallback.this.onChildrenLoaded(
-                                    parentId, applyOptions(itemList, options), options);
-                        }
-                    }
-                }
-            }
-
-            @Override
-            public void onError(@NonNull String parentId) {
-                SubscriptionCallback.this.onError(parentId);
-            }
-
-            List<MediaBrowserCompat.MediaItem> applyOptions(List<MediaBrowserCompat.MediaItem> list,
-                    final Bundle options) {
-                if (list == null) {
-                    return null;
-                }
-                int page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
-                int pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
-                if (page == -1 && pageSize == -1) {
-                    return list;
-                }
-                int fromIndex = pageSize * page;
-                int toIndex = fromIndex + pageSize;
-                if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
-                    return Collections.EMPTY_LIST;
-                }
-                if (toIndex > list.size()) {
-                    toIndex = list.size();
-                }
-                return list.subList(fromIndex, toIndex);
-            }
-
-        }
-
-        private class StubApi26 extends StubApi21
-                implements MediaBrowserCompatApi26.SubscriptionCallback {
-            StubApi26() {
-            }
-
-            @Override
-            public void onChildrenLoaded(@NonNull String parentId, List<?> children,
-                    @NonNull Bundle options) {
-                SubscriptionCallback.this.onChildrenLoaded(
-                        parentId, MediaItem.fromMediaItemList(children), options);
-            }
-
-            @Override
-            public void onError(@NonNull String parentId, @NonNull Bundle options) {
-                SubscriptionCallback.this.onError(parentId, options);
-            }
-        }
-    }
-
-    /**
-     * Callback for receiving the result of {@link #getItem}.
-     */
-    public static abstract class ItemCallback {
-        final Object mItemCallbackObj;
-
-        public ItemCallback() {
-            if (Build.VERSION.SDK_INT >= 23) {
-                mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
-            } else {
-                mItemCallbackObj = null;
-            }
-        }
-
-        /**
-         * Called when the item has been returned by the browser service.
-         *
-         * @param item The item that was returned or null if it doesn't exist.
-         */
-        public void onItemLoaded(MediaItem item) {
-        }
-
-        /**
-         * Called when the item doesn't exist or there was an error retrieving it.
-         *
-         * @param itemId The media id of the media item which could not be loaded.
-         */
-        public void onError(@NonNull String itemId) {
-        }
-
-        private class StubApi23 implements MediaBrowserCompatApi23.ItemCallback {
-            StubApi23() {
-            }
-
-            @Override
-            public void onItemLoaded(Parcel itemParcel) {
-                if (itemParcel == null) {
-                    ItemCallback.this.onItemLoaded(null);
-                } else {
-                    itemParcel.setDataPosition(0);
-                    MediaItem item =
-                            MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(itemParcel);
-                    itemParcel.recycle();
-                    ItemCallback.this.onItemLoaded(item);
-                }
-            }
-
-            @Override
-            public void onError(@NonNull String itemId) {
-                ItemCallback.this.onError(itemId);
-            }
-        }
-    }
-
-    /**
-     * Callback for receiving the result of {@link #search}.
-     */
-    public abstract static class SearchCallback {
-        /**
-         * Called when the {@link #search} finished successfully.
-         *
-         * @param query The search query sent for the search request to the connected service.
-         * @param extras The bundle of service-specific arguments sent to the connected service.
-         * @param items The list of media items which contains the search result.
-         */
-        public void onSearchResult(@NonNull String query, Bundle extras,
-                @NonNull List<MediaItem> items) {
-        }
-
-        /**
-         * Called when an error happens while {@link #search} or the connected service doesn't
-         * support {@link #search}.
-         *
-         * @param query The search query sent for the search request to the connected service.
-         * @param extras The bundle of service-specific arguments sent to the connected service.
-         */
-        public void onError(@NonNull String query, Bundle extras) {
-        }
-    }
-
-    /**
-     * Callback for receiving the result of {@link #sendCustomAction}.
-     */
-    public abstract static class CustomActionCallback {
-        /**
-         * Called when an interim update was delivered from the connected service while performing
-         * the custom action.
-         *
-         * @param action The custom action sent to the connected service.
-         * @param extras The bundle of service-specific arguments sent to the connected service.
-         * @param data The additional data delivered from the connected service.
-         */
-        public void onProgressUpdate(String action, Bundle extras, Bundle data) {
-        }
-
-        /**
-         * Called when the custom action finished successfully.
-         *
-         * @param action The custom action sent to the connected service.
-         * @param extras The bundle of service-specific arguments sent to the connected service.
-         * @param resultData The additional data delivered from the connected service.
-         */
-        public void onResult(String action, Bundle extras, Bundle resultData) {
-        }
-
-        /**
-         * Called when an error happens while performing the custom action or the connected service
-         * doesn't support the requested custom action.
-         *
-         * @param action The custom action sent to the connected service.
-         * @param extras The bundle of service-specific arguments sent to the connected service.
-         * @param data The additional data delivered from the connected service.
-         */
-        public void onError(String action, Bundle extras, Bundle data) {
-        }
-    }
-
-    interface MediaBrowserImpl {
-        void connect();
-        void disconnect();
-        boolean isConnected();
-        ComponentName getServiceComponent();
-        @NonNull String getRoot();
-        @Nullable Bundle getExtras();
-        @NonNull MediaSessionCompat.Token getSessionToken();
-        void subscribe(@NonNull String parentId, @Nullable Bundle options,
-                @NonNull SubscriptionCallback callback);
-        void unsubscribe(@NonNull String parentId, SubscriptionCallback callback);
-        void getItem(@NonNull String mediaId, @NonNull ItemCallback cb);
-        void search(@NonNull String query, Bundle extras, @NonNull SearchCallback callback);
-        void sendCustomAction(@NonNull String action, Bundle extras,
-                @Nullable CustomActionCallback callback);
-    }
-
-    interface MediaBrowserServiceCallbackImpl {
-        void onServiceConnected(Messenger callback, String root, MediaSessionCompat.Token session,
-                Bundle extra);
-        void onConnectionFailed(Messenger callback);
-        void onLoadChildren(Messenger callback, String parentId, List list, Bundle options);
-    }
-
-    static class MediaBrowserImplBase
-            implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl {
-        static final int CONNECT_STATE_DISCONNECTING = 0;
-        static final int CONNECT_STATE_DISCONNECTED = 1;
-        static final int CONNECT_STATE_CONNECTING = 2;
-        static final int CONNECT_STATE_CONNECTED = 3;
-        static final int CONNECT_STATE_SUSPENDED = 4;
-
-        final Context mContext;
-        final ComponentName mServiceComponent;
-        final ConnectionCallback mCallback;
-        final Bundle mRootHints;
-        final CallbackHandler mHandler = new CallbackHandler(this);
-        private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
-
-        int mState = CONNECT_STATE_DISCONNECTED;
-        MediaServiceConnection mServiceConnection;
-        ServiceBinderWrapper mServiceBinderWrapper;
-        Messenger mCallbacksMessenger;
-        private String mRootId;
-        private MediaSessionCompat.Token mMediaSessionToken;
-        private Bundle mExtras;
-
-        public MediaBrowserImplBase(Context context, ComponentName serviceComponent,
-                ConnectionCallback callback, Bundle rootHints) {
-            if (context == null) {
-                throw new IllegalArgumentException("context must not be null");
-            }
-            if (serviceComponent == null) {
-                throw new IllegalArgumentException("service component must not be null");
-            }
-            if (callback == null) {
-                throw new IllegalArgumentException("connection callback must not be null");
-            }
-            mContext = context;
-            mServiceComponent = serviceComponent;
-            mCallback = callback;
-            mRootHints = rootHints == null ? null : new Bundle(rootHints);
-        }
-
-        @Override
-        public void connect() {
-            if (mState != CONNECT_STATE_DISCONNECTING && mState != CONNECT_STATE_DISCONNECTED) {
-                throw new IllegalStateException("connect() called while neigther disconnecting nor "
-                        + "disconnected (state=" + getStateLabel(mState) + ")");
-            }
-
-            mState = CONNECT_STATE_CONNECTING;
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    // mState could be changed by the Runnable of disconnect()
-                    if (mState == CONNECT_STATE_DISCONNECTING) {
-                        return;
-                    }
-                    mState = CONNECT_STATE_CONNECTING;
-                    // TODO: remove this extra check.
-                    if (DEBUG) {
-                        if (mServiceConnection != null) {
-                            throw new RuntimeException("mServiceConnection should be null. Instead "
-                                    + "it is " + mServiceConnection);
-                        }
-                    }
-                    if (mServiceBinderWrapper != null) {
-                        throw new RuntimeException("mServiceBinderWrapper should be null. Instead "
-                                + "it is " + mServiceBinderWrapper);
-                    }
-                    if (mCallbacksMessenger != null) {
-                        throw new RuntimeException("mCallbacksMessenger should be null. Instead "
-                                + "it is " + mCallbacksMessenger);
-                    }
-
-                    final Intent intent = new Intent(MediaBrowserServiceCompat.SERVICE_INTERFACE);
-                    intent.setComponent(mServiceComponent);
-
-                    mServiceConnection = new MediaServiceConnection();
-                    boolean bound = false;
-                    try {
-                        bound = mContext.bindService(intent, mServiceConnection,
-                                Context.BIND_AUTO_CREATE);
-                    } catch (Exception ex) {
-                        Log.e(TAG, "Failed binding to service " + mServiceComponent);
-                    }
-
-                    if (!bound) {
-                        // Tell them that it didn't work.
-                        forceCloseConnection();
-                        mCallback.onConnectionFailed();
-                    }
-
-                    if (DEBUG) {
-                        Log.d(TAG, "connect...");
-                        dump();
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void disconnect() {
-            // It's ok to call this any state, because allowing this lets apps not have
-            // to check isConnected() unnecessarily. They won't appreciate the extra
-            // assertions for this. We do everything we can here to go back to a sane state.
-            mState = CONNECT_STATE_DISCONNECTING;
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    // connect() could be called before this. Then we will disconnect and reconnect.
-                    if (mCallbacksMessenger != null) {
-                        try {
-                            mServiceBinderWrapper.disconnect(mCallbacksMessenger);
-                        } catch (RemoteException ex) {
-                            // We are disconnecting anyway. Log, just for posterity but it's not
-                            // a big problem.
-                            Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
-                        }
-                    }
-                    int state = mState;
-                    forceCloseConnection();
-                    // If the state was not CONNECT_STATE_DISCONNECTING, keep the state so that
-                    // the operation came after disconnect() can be handled properly.
-                    if (state != CONNECT_STATE_DISCONNECTING) {
-                        mState = state;
-                    }
-                    if (DEBUG) {
-                        Log.d(TAG, "disconnect...");
-                        dump();
-                    }
-                }
-            });
-        }
-
-        /**
-         * Null out the variables and unbind from the service. This doesn't include
-         * calling disconnect on the service, because we only try to do that in the
-         * clean shutdown cases.
-         * <p>
-         * Everywhere that calls this EXCEPT for disconnect() should follow it with
-         * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback
-         * for a clean shutdown, but everywhere else is a dirty shutdown and should
-         * notify the app.
-         */
-        void forceCloseConnection() {
-            if (mServiceConnection != null) {
-                mContext.unbindService(mServiceConnection);
-            }
-            mState = CONNECT_STATE_DISCONNECTED;
-            mServiceConnection = null;
-            mServiceBinderWrapper = null;
-            mCallbacksMessenger = null;
-            mHandler.setCallbacksMessenger(null);
-            mRootId = null;
-            mMediaSessionToken = null;
-        }
-
-        @Override
-        public boolean isConnected() {
-            return mState == CONNECT_STATE_CONNECTED;
-        }
-
-        @Override
-        public @NonNull ComponentName getServiceComponent() {
-            if (!isConnected()) {
-                throw new IllegalStateException("getServiceComponent() called while not connected" +
-                        " (state=" + mState + ")");
-            }
-            return mServiceComponent;
-        }
-
-        @Override
-        public @NonNull String getRoot() {
-            if (!isConnected()) {
-                throw new IllegalStateException("getRoot() called while not connected"
-                        + "(state=" + getStateLabel(mState) + ")");
-            }
-            return mRootId;
-        }
-
-        @Override
-        public @Nullable Bundle getExtras() {
-            if (!isConnected()) {
-                throw new IllegalStateException("getExtras() called while not connected (state="
-                        + getStateLabel(mState) + ")");
-            }
-            return mExtras;
-        }
-
-        @Override
-        public @NonNull MediaSessionCompat.Token getSessionToken() {
-            if (!isConnected()) {
-                throw new IllegalStateException("getSessionToken() called while not connected"
-                        + "(state=" + mState + ")");
-            }
-            return mMediaSessionToken;
-        }
-
-        @Override
-        public void subscribe(@NonNull String parentId, Bundle options,
-                @NonNull SubscriptionCallback callback) {
-            // Update or create the subscription.
-            Subscription sub = mSubscriptions.get(parentId);
-            if (sub == null) {
-                sub = new Subscription();
-                mSubscriptions.put(parentId, sub);
-            }
-            Bundle copiedOptions = options == null ? null : new Bundle(options);
-            sub.putCallback(mContext, copiedOptions, callback);
-
-            // If we are connected, tell the service that we are watching. If we aren't
-            // connected, the service will be told when we connect.
-            if (isConnected()) {
-                try {
-                    mServiceBinderWrapper.addSubscription(parentId, callback.mToken, copiedOptions,
-                            mCallbacksMessenger);
-                } catch (RemoteException e) {
-                    // Process is crashing. We will disconnect, and upon reconnect we will
-                    // automatically reregister. So nothing to do here.
-                    Log.d(TAG, "addSubscription failed with RemoteException parentId=" + parentId);
-                }
-            }
-        }
-
-        @Override
-        public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
-            Subscription sub = mSubscriptions.get(parentId);
-            if (sub == null) {
-                return;
-            }
-
-            // Tell the service if necessary.
-            try {
-                if (callback == null) {
-                    if (isConnected()) {
-                        mServiceBinderWrapper.removeSubscription(parentId, null,
-                                mCallbacksMessenger);
-                    }
-                } else {
-                    final List<SubscriptionCallback> callbacks = sub.getCallbacks();
-                    final List<Bundle> optionsList = sub.getOptionsList();
-                    for (int i = callbacks.size() - 1; i >= 0; --i) {
-                        if (callbacks.get(i) == callback) {
-                            if (isConnected()) {
-                                mServiceBinderWrapper.removeSubscription(
-                                        parentId, callback.mToken, mCallbacksMessenger);
-                            }
-                            callbacks.remove(i);
-                            optionsList.remove(i);
-                        }
-                    }
-                }
-            } catch (RemoteException ex) {
-                // Process is crashing. We will disconnect, and upon reconnect we will
-                // automatically reregister. So nothing to do here.
-                Log.d(TAG, "removeSubscription failed with RemoteException parentId=" + parentId);
-            }
-
-            if (sub.isEmpty() || callback == null) {
-                mSubscriptions.remove(parentId);
-            }
-        }
-
-        @Override
-        public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
-            if (TextUtils.isEmpty(mediaId)) {
-                throw new IllegalArgumentException("mediaId is empty");
-            }
-            if (cb == null) {
-                throw new IllegalArgumentException("cb is null");
-            }
-            if (!isConnected()) {
-                Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        cb.onError(mediaId);
-                    }
-                });
-                return;
-            }
-            ResultReceiver receiver = new ItemReceiver(mediaId, cb, mHandler);
-            try {
-                mServiceBinderWrapper.getMediaItem(mediaId, receiver, mCallbacksMessenger);
-            } catch (RemoteException e) {
-                Log.i(TAG, "Remote error getting media item: " + mediaId);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        cb.onError(mediaId);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void search(@NonNull final String query, final Bundle extras,
-                @NonNull final SearchCallback callback) {
-            if (!isConnected()) {
-                throw new IllegalStateException("search() called while not connected"
-                        + " (state=" + getStateLabel(mState) + ")");
-            }
-
-            ResultReceiver receiver = new SearchResultReceiver(query, extras, callback, mHandler);
-            try {
-                mServiceBinderWrapper.search(query, extras, receiver, mCallbacksMessenger);
-            } catch (RemoteException e) {
-                Log.i(TAG, "Remote error searching items with query: " + query, e);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        callback.onError(query, extras);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void sendCustomAction(@NonNull final String action, final Bundle extras,
-                @Nullable final CustomActionCallback callback) {
-            if (!isConnected()) {
-                throw new IllegalStateException("Cannot send a custom action (" + action + ") with "
-                        + "extras " + extras + " because the browser is not connected to the "
-                        + "service.");
-            }
-
-            ResultReceiver receiver = new CustomActionResultReceiver(action, extras, callback,
-                    mHandler);
-            try {
-                mServiceBinderWrapper.sendCustomAction(action, extras, receiver,
-                        mCallbacksMessenger);
-            } catch (RemoteException e) {
-                Log.i(TAG, "Remote error sending a custom action: action=" + action + ", extras="
-                        + extras, e);
-                if (callback != null) {
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            callback.onError(action, extras, null);
-                        }
-                    });
-                }
-            }
-        }
-
-        @Override
-        public void onServiceConnected(final Messenger callback, final String root,
-                final MediaSessionCompat.Token session, final Bundle extra) {
-            // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
-            if (!isCurrent(callback, "onConnect")) {
-                return;
-            }
-            // Don't allow them to call us twice.
-            if (mState != CONNECT_STATE_CONNECTING) {
-                Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
-                        + "... ignoring");
-                return;
-            }
-            mRootId = root;
-            mMediaSessionToken = session;
-            mExtras = extra;
-            mState = CONNECT_STATE_CONNECTED;
-
-            if (DEBUG) {
-                Log.d(TAG, "ServiceCallbacks.onConnect...");
-                dump();
-            }
-            mCallback.onConnected();
-
-            // we may receive some subscriptions before we are connected, so re-subscribe
-            // everything now
-            try {
-                for (Map.Entry<String, Subscription> subscriptionEntry
-                        : mSubscriptions.entrySet()) {
-                    String id = subscriptionEntry.getKey();
-                    Subscription sub = subscriptionEntry.getValue();
-                    List<SubscriptionCallback> callbackList = sub.getCallbacks();
-                    List<Bundle> optionsList = sub.getOptionsList();
-                    for (int i = 0; i < callbackList.size(); ++i) {
-                        mServiceBinderWrapper.addSubscription(id, callbackList.get(i).mToken,
-                                optionsList.get(i), mCallbacksMessenger);
-                    }
-                }
-            } catch (RemoteException ex) {
-                // Process is crashing. We will disconnect, and upon reconnect we will
-                // automatically reregister. So nothing to do here.
-                Log.d(TAG, "addSubscription failed with RemoteException.");
-            }
-        }
-
-        @Override
-        public void onConnectionFailed(final Messenger callback) {
-            Log.e(TAG, "onConnectFailed for " + mServiceComponent);
-
-            // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
-            if (!isCurrent(callback, "onConnectFailed")) {
-                return;
-            }
-            // Don't allow them to call us twice.
-            if (mState != CONNECT_STATE_CONNECTING) {
-                Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
-                        + "... ignoring");
-                return;
-            }
-
-            // Clean up
-            forceCloseConnection();
-
-            // Tell the app.
-            mCallback.onConnectionFailed();
-        }
-
-        @Override
-        public void onLoadChildren(final Messenger callback, final String parentId,
-                final List list, final Bundle options) {
-            // Check that there hasn't been a disconnect or a different ServiceConnection.
-            if (!isCurrent(callback, "onLoadChildren")) {
-                return;
-            }
-
-            if (DEBUG) {
-                Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
-            }
-
-            // Check that the subscription is still subscribed.
-            final Subscription subscription = mSubscriptions.get(parentId);
-            if (subscription == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
-                }
-                return;
-            }
-
-            // Tell the app.
-            SubscriptionCallback subscriptionCallback = subscription.getCallback(mContext, options);
-            if (subscriptionCallback != null) {
-                if (options == null) {
-                    if (list == null) {
-                        subscriptionCallback.onError(parentId);
-                    } else {
-                        subscriptionCallback.onChildrenLoaded(parentId, list);
-                    }
-                } else {
-                    if (list == null) {
-                        subscriptionCallback.onError(parentId, options);
-                    } else {
-                        subscriptionCallback.onChildrenLoaded(parentId, list, options);
-                    }
-                }
-            }
-        }
-
-        /**
-         * For debugging.
-         */
-        private static String getStateLabel(int state) {
-            switch (state) {
-                case CONNECT_STATE_DISCONNECTING:
-                    return "CONNECT_STATE_DISCONNECTING";
-                case CONNECT_STATE_DISCONNECTED:
-                    return "CONNECT_STATE_DISCONNECTED";
-                case CONNECT_STATE_CONNECTING:
-                    return "CONNECT_STATE_CONNECTING";
-                case CONNECT_STATE_CONNECTED:
-                    return "CONNECT_STATE_CONNECTED";
-                case CONNECT_STATE_SUSPENDED:
-                    return "CONNECT_STATE_SUSPENDED";
-                default:
-                    return "UNKNOWN/" + state;
-            }
-        }
-
-        /**
-         * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
-         */
-        @SuppressWarnings("ReferenceEquality")
-        private boolean isCurrent(Messenger callback, String funcName) {
-            if (mCallbacksMessenger != callback || mState == CONNECT_STATE_DISCONNECTING
-                    || mState == CONNECT_STATE_DISCONNECTED) {
-                if (mState != CONNECT_STATE_DISCONNECTING && mState != CONNECT_STATE_DISCONNECTED) {
-                    Log.i(TAG, funcName + " for " + mServiceComponent + " with mCallbacksMessenger="
-                            + mCallbacksMessenger + " this=" + this);
-                }
-                return false;
-            }
-            return true;
-        }
-
-        /**
-         * Log internal state.
-         */
-        void dump() {
-            Log.d(TAG, "MediaBrowserCompat...");
-            Log.d(TAG, "  mServiceComponent=" + mServiceComponent);
-            Log.d(TAG, "  mCallback=" + mCallback);
-            Log.d(TAG, "  mRootHints=" + mRootHints);
-            Log.d(TAG, "  mState=" + getStateLabel(mState));
-            Log.d(TAG, "  mServiceConnection=" + mServiceConnection);
-            Log.d(TAG, "  mServiceBinderWrapper=" + mServiceBinderWrapper);
-            Log.d(TAG, "  mCallbacksMessenger=" + mCallbacksMessenger);
-            Log.d(TAG, "  mRootId=" + mRootId);
-            Log.d(TAG, "  mMediaSessionToken=" + mMediaSessionToken);
-        }
-
-        /**
-         * ServiceConnection to the other app.
-         */
-        private class MediaServiceConnection implements ServiceConnection {
-            MediaServiceConnection() {
-            }
-
-            @Override
-            public void onServiceConnected(final ComponentName name, final IBinder binder) {
-                postOrRun(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (DEBUG) {
-                            Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
-                                    + " binder=" + binder);
-                            dump();
-                        }
-
-                        // Make sure we are still the current connection, and that they haven't
-                        // called disconnect().
-                        if (!isCurrent("onServiceConnected")) {
-                            return;
-                        }
-
-                        // Save their binder
-                        mServiceBinderWrapper = new ServiceBinderWrapper(binder, mRootHints);
-
-                        // We make a new mServiceCallbacks each time we connect so that we can drop
-                        // responses from previous connections.
-                        mCallbacksMessenger = new Messenger(mHandler);
-                        mHandler.setCallbacksMessenger(mCallbacksMessenger);
-
-                        mState = CONNECT_STATE_CONNECTING;
-
-                        // Call connect, which is async. When we get a response from that we will
-                        // say that we're connected.
-                        try {
-                            if (DEBUG) {
-                                Log.d(TAG, "ServiceCallbacks.onConnect...");
-                                dump();
-                            }
-                            mServiceBinderWrapper.connect(mContext, mCallbacksMessenger);
-                        } catch (RemoteException ex) {
-                            // Connect failed, which isn't good. But the auto-reconnect on the
-                            // service will take over and we will come back. We will also get the
-                            // onServiceDisconnected, which has all the cleanup code. So let that
-                            // do it.
-                            Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
-                            if (DEBUG) {
-                                Log.d(TAG, "ServiceCallbacks.onConnect...");
-                                dump();
-                            }
-                        }
-                    }
-                });
-            }
-
-            @Override
-            public void onServiceDisconnected(final ComponentName name) {
-                postOrRun(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (DEBUG) {
-                            Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
-                                    + " this=" + this + " mServiceConnection=" +
-                                    mServiceConnection);
-                            dump();
-                        }
-
-                        // Make sure we are still the current connection, and that they haven't
-                        // called disconnect().
-                        if (!isCurrent("onServiceDisconnected")) {
-                            return;
-                        }
-
-                        // Clear out what we set in onServiceConnected
-                        mServiceBinderWrapper = null;
-                        mCallbacksMessenger = null;
-                        mHandler.setCallbacksMessenger(null);
-
-                        // And tell the app that it's suspended.
-                        mState = CONNECT_STATE_SUSPENDED;
-                        mCallback.onConnectionSuspended();
-                    }
-                });
-            }
-
-            private void postOrRun(Runnable r) {
-                if (Thread.currentThread() == mHandler.getLooper().getThread()) {
-                    r.run();
-                } else {
-                    mHandler.post(r);
-                }
-            }
-
-            /**
-             * Return true if this is the current ServiceConnection. Also logs if it's not.
-             */
-            boolean isCurrent(String funcName) {
-                if (mServiceConnection != this || mState == CONNECT_STATE_DISCONNECTING
-                        || mState == CONNECT_STATE_DISCONNECTED) {
-                    if (mState != CONNECT_STATE_DISCONNECTING
-                            && mState != CONNECT_STATE_DISCONNECTED) {
-                        // Check mState, because otherwise this log is noisy.
-                        Log.i(TAG, funcName + " for " + mServiceComponent +
-                                " with mServiceConnection=" + mServiceConnection + " this=" + this);
-                    }
-                    return false;
-                }
-                return true;
-            }
-        }
-    }
-
-    @RequiresApi(21)
-    static class MediaBrowserImplApi21 implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl,
-            ConnectionCallback.ConnectionCallbackInternal {
-        final Context mContext;
-        protected final Object mBrowserObj;
-        protected final Bundle mRootHints;
-        protected final CallbackHandler mHandler = new CallbackHandler(this);
-        private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
-
-        protected int mServiceVersion;
-        protected ServiceBinderWrapper mServiceBinderWrapper;
-        protected Messenger mCallbacksMessenger;
-        private MediaSessionCompat.Token mMediaSessionToken;
-
-        MediaBrowserImplApi21(Context context, ComponentName serviceComponent,
-                ConnectionCallback callback, Bundle rootHints) {
-            mContext = context;
-            if (rootHints == null) {
-                rootHints = new Bundle();
-            }
-            rootHints.putInt(EXTRA_CLIENT_VERSION, CLIENT_VERSION_CURRENT);
-            mRootHints = new Bundle(rootHints);
-            callback.setInternalConnectionCallback(this);
-            mBrowserObj = MediaBrowserCompatApi21.createBrowser(context, serviceComponent,
-                    callback.mConnectionCallbackObj, mRootHints);
-        }
-
-        @Override
-        public void connect() {
-            MediaBrowserCompatApi21.connect(mBrowserObj);
-        }
-
-        @Override
-        public void disconnect() {
-            if (mServiceBinderWrapper != null && mCallbacksMessenger != null) {
-                try {
-                    mServiceBinderWrapper.unregisterCallbackMessenger(mCallbacksMessenger);
-                } catch (RemoteException e) {
-                    Log.i(TAG, "Remote error unregistering client messenger." );
-                }
-            }
-            MediaBrowserCompatApi21.disconnect(mBrowserObj);
-        }
-
-        @Override
-        public boolean isConnected() {
-            return MediaBrowserCompatApi21.isConnected(mBrowserObj);
-        }
-
-        @Override
-        public ComponentName getServiceComponent() {
-            return MediaBrowserCompatApi21.getServiceComponent(mBrowserObj);
-        }
-
-        @NonNull
-        @Override
-        public String getRoot() {
-            return MediaBrowserCompatApi21.getRoot(mBrowserObj);
-        }
-
-        @Nullable
-        @Override
-        public Bundle getExtras() {
-            return MediaBrowserCompatApi21.getExtras(mBrowserObj);
-        }
-
-        @NonNull
-        @Override
-        public MediaSessionCompat.Token getSessionToken() {
-            if (mMediaSessionToken == null) {
-                mMediaSessionToken = MediaSessionCompat.Token.fromToken(
-                        MediaBrowserCompatApi21.getSessionToken(mBrowserObj));
-            }
-            return mMediaSessionToken;
-        }
-
-        @Override
-        public void subscribe(@NonNull final String parentId, final Bundle options,
-                @NonNull final SubscriptionCallback callback) {
-            // Update or create the subscription.
-            Subscription sub = mSubscriptions.get(parentId);
-            if (sub == null) {
-                sub = new Subscription();
-                mSubscriptions.put(parentId, sub);
-            }
-            callback.setSubscription(sub);
-            Bundle copiedOptions = options == null ? null : new Bundle(options);
-            sub.putCallback(mContext, copiedOptions, callback);
-
-            if (mServiceBinderWrapper == null) {
-                // TODO: When MediaBrowser is connected to framework's MediaBrowserService,
-                // subscribe with options won't work properly.
-                MediaBrowserCompatApi21.subscribe(
-                        mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
-            } else {
-                try {
-                    mServiceBinderWrapper.addSubscription(
-                            parentId, callback.mToken, copiedOptions, mCallbacksMessenger);
-                } catch (RemoteException e) {
-                    // Process is crashing. We will disconnect, and upon reconnect we will
-                    // automatically reregister. So nothing to do here.
-                    Log.i(TAG, "Remote error subscribing media item: " + parentId);
-                }
-            }
-        }
-
-        @Override
-        public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
-            Subscription sub = mSubscriptions.get(parentId);
-            if (sub == null) {
-                return;
-            }
-
-            if (mServiceBinderWrapper == null) {
-                if (callback == null) {
-                    MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
-                } else {
-                    final List<SubscriptionCallback> callbacks = sub.getCallbacks();
-                    final List<Bundle> optionsList = sub.getOptionsList();
-                    for (int i = callbacks.size() - 1; i >= 0; --i) {
-                        if (callbacks.get(i) == callback) {
-                            callbacks.remove(i);
-                            optionsList.remove(i);
-                        }
-                    }
-                    if (callbacks.size() == 0) {
-                        MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
-                    }
-                }
-            } else {
-                // Tell the service if necessary.
-                try {
-                    if (callback == null) {
-                        mServiceBinderWrapper.removeSubscription(parentId, null,
-                                mCallbacksMessenger);
-                    } else {
-                        final List<SubscriptionCallback> callbacks = sub.getCallbacks();
-                        final List<Bundle> optionsList = sub.getOptionsList();
-                        for (int i = callbacks.size() - 1; i >= 0; --i) {
-                            if (callbacks.get(i) == callback) {
-                                mServiceBinderWrapper.removeSubscription(
-                                        parentId, callback.mToken, mCallbacksMessenger);
-                                callbacks.remove(i);
-                                optionsList.remove(i);
-                            }
-                        }
-                    }
-                } catch (RemoteException ex) {
-                    // Process is crashing. We will disconnect, and upon reconnect we will
-                    // automatically reregister. So nothing to do here.
-                    Log.d(TAG, "removeSubscription failed with RemoteException parentId="
-                            + parentId);
-                }
-            }
-
-            if (sub.isEmpty() || callback == null) {
-                mSubscriptions.remove(parentId);
-            }
-        }
-
-        @Override
-        public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
-            if (TextUtils.isEmpty(mediaId)) {
-                throw new IllegalArgumentException("mediaId is empty");
-            }
-            if (cb == null) {
-                throw new IllegalArgumentException("cb is null");
-            }
-            if (!MediaBrowserCompatApi21.isConnected(mBrowserObj)) {
-                Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        cb.onError(mediaId);
-                    }
-                });
-                return;
-            }
-            if (mServiceBinderWrapper == null) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Default framework implementation.
-                        cb.onError(mediaId);
-                    }
-                });
-                return;
-            }
-            ResultReceiver receiver = new ItemReceiver(mediaId, cb, mHandler);
-            try {
-                mServiceBinderWrapper.getMediaItem(mediaId, receiver, mCallbacksMessenger);
-            } catch (RemoteException e) {
-                Log.i(TAG, "Remote error getting media item: " + mediaId);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        cb.onError(mediaId);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void search(@NonNull final String query, final Bundle extras,
-                @NonNull final SearchCallback callback) {
-            if (!isConnected()) {
-                throw new IllegalStateException("search() called while not connected");
-            }
-            if (mServiceBinderWrapper == null) {
-                Log.i(TAG, "The connected service doesn't support search.");
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Default framework implementation.
-                        callback.onError(query, extras);
-                    }
-                });
-                return;
-            }
-
-            ResultReceiver receiver = new SearchResultReceiver(query, extras, callback, mHandler);
-            try {
-                mServiceBinderWrapper.search(query, extras, receiver, mCallbacksMessenger);
-            } catch (RemoteException e) {
-                Log.i(TAG, "Remote error searching items with query: " + query, e);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        callback.onError(query, extras);
-                    }
-                });
-            }
-        }
-
-        @Override
-        public void sendCustomAction(@NonNull final String action, final Bundle extras,
-                @Nullable final CustomActionCallback callback) {
-            if (!isConnected()) {
-                throw new IllegalStateException("Cannot send a custom action (" + action + ") with "
-                        + "extras " + extras + " because the browser is not connected to the "
-                        + "service.");
-            }
-            if (mServiceBinderWrapper == null) {
-                Log.i(TAG, "The connected service doesn't support sendCustomAction.");
-                if (callback != null) {
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            callback.onError(action, extras, null);
-                        }
-                    });
-                }
-            }
-
-            ResultReceiver receiver = new CustomActionResultReceiver(action, extras, callback,
-                    mHandler);
-            try {
-                mServiceBinderWrapper.sendCustomAction(action, extras, receiver,
-                        mCallbacksMessenger);
-            } catch (RemoteException e) {
-                Log.i(TAG, "Remote error sending a custom action: action=" + action + ", extras="
-                        + extras, e);
-                if (callback != null) {
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            callback.onError(action, extras, null);
-                        }
-                    });
-                }
-            }
-        }
-
-        @Override
-        public void onConnected() {
-            Bundle extras = MediaBrowserCompatApi21.getExtras(mBrowserObj);
-            if (extras == null) {
-                return;
-            }
-            mServiceVersion = extras.getInt(EXTRA_SERVICE_VERSION, 0);
-            IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
-            if (serviceBinder != null) {
-                mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints);
-                mCallbacksMessenger = new Messenger(mHandler);
-                mHandler.setCallbacksMessenger(mCallbacksMessenger);
-                try {
-                    mServiceBinderWrapper.registerCallbackMessenger(mCallbacksMessenger);
-                } catch (RemoteException e) {
-                    Log.i(TAG, "Remote error registering client messenger." );
-                }
-            }
-            IMediaSession sessionToken = IMediaSession.Stub.asInterface(
-                    BundleCompat.getBinder(extras, EXTRA_SESSION_BINDER));
-            if (sessionToken != null) {
-                mMediaSessionToken = MediaSessionCompat.Token.fromToken(
-                        MediaBrowserCompatApi21.getSessionToken(mBrowserObj), sessionToken);
-            }
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            mServiceBinderWrapper = null;
-            mCallbacksMessenger = null;
-            mMediaSessionToken = null;
-            mHandler.setCallbacksMessenger(null);
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            // Do noting
-        }
-
-        @Override
-        public void onServiceConnected(final Messenger callback, final String root,
-                final MediaSessionCompat.Token session, final Bundle extra) {
-            // This method will not be called.
-        }
-
-        @Override
-        public void onConnectionFailed(Messenger callback) {
-            // This method will not be called.
-        }
-
-        @Override
-        @SuppressWarnings("ReferenceEquality")
-        public void onLoadChildren(Messenger callback, String parentId, List list, Bundle options) {
-            if (mCallbacksMessenger != callback) {
-                return;
-            }
-
-            // Check that the subscription is still subscribed.
-            Subscription subscription = mSubscriptions.get(parentId);
-            if (subscription == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
-                }
-                return;
-            }
-
-            // Tell the app.
-            SubscriptionCallback subscriptionCallback = subscription.getCallback(mContext, options);
-            if (subscriptionCallback != null) {
-                if (options == null) {
-                    if (list == null) {
-                        subscriptionCallback.onError(parentId);
-                    } else {
-                        subscriptionCallback.onChildrenLoaded(parentId, list);
-                    }
-                } else {
-                    if (list == null) {
-                        subscriptionCallback.onError(parentId, options);
-                    } else {
-                        subscriptionCallback.onChildrenLoaded(parentId, list, options);
-                    }
-                }
-            }
-        }
-    }
-
-    @RequiresApi(23)
-    static class MediaBrowserImplApi23 extends MediaBrowserImplApi21 {
-        MediaBrowserImplApi23(Context context, ComponentName serviceComponent,
-                ConnectionCallback callback, Bundle rootHints) {
-            super(context, serviceComponent, callback, rootHints);
-        }
-
-        @Override
-        public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
-            if (mServiceBinderWrapper == null) {
-                MediaBrowserCompatApi23.getItem(mBrowserObj, mediaId, cb.mItemCallbackObj);
-            } else {
-                super.getItem(mediaId, cb);
-            }
-        }
-    }
-
-    @RequiresApi(26)
-    static class MediaBrowserImplApi26 extends MediaBrowserImplApi23 {
-        MediaBrowserImplApi26(Context context, ComponentName serviceComponent,
-                ConnectionCallback callback, Bundle rootHints) {
-            super(context, serviceComponent, callback, rootHints);
-        }
-
-        @Override
-        public void subscribe(@NonNull String parentId, @Nullable Bundle options,
-                @NonNull SubscriptionCallback callback) {
-            // From service v2, we use compat code when subscribing.
-            // This is to prevent ClassNotFoundException when options has Parcelable in it.
-            if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
-                if (options == null) {
-                    MediaBrowserCompatApi21.subscribe(
-                            mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
-                } else {
-                    MediaBrowserCompatApi26.subscribe(
-                            mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
-                }
-            } else {
-                super.subscribe(parentId, options, callback);
-            }
-        }
-
-        @Override
-        public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
-            // From service v2, we use compat code when subscribing.
-            // This is to prevent ClassNotFoundException when options has Parcelable in it.
-            if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
-                if (callback == null) {
-                    MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
-                } else {
-                    MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
-                            callback.mSubscriptionCallbackObj);
-                }
-            } else {
-                super.unsubscribe(parentId, callback);
-            }
-        }
-    }
-
-    private static class Subscription {
-        private final List<SubscriptionCallback> mCallbacks;
-        private final List<Bundle> mOptionsList;
-
-        public Subscription() {
-            mCallbacks = new ArrayList<>();
-            mOptionsList = new ArrayList<>();
-        }
-
-        public boolean isEmpty() {
-            return mCallbacks.isEmpty();
-        }
-
-        public List<Bundle> getOptionsList() {
-            return mOptionsList;
-        }
-
-        public List<SubscriptionCallback> getCallbacks() {
-            return mCallbacks;
-        }
-
-        public SubscriptionCallback getCallback(Context context, Bundle options) {
-            if (options != null) {
-                options.setClassLoader(context.getClassLoader());
-            }
-            for (int i = 0; i < mOptionsList.size(); ++i) {
-                if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
-                    return mCallbacks.get(i);
-                }
-            }
-            return null;
-        }
-
-        public void putCallback(Context context, Bundle options, SubscriptionCallback callback) {
-            if (options != null) {
-                options.setClassLoader(context.getClassLoader());
-            }
-            for (int i = 0; i < mOptionsList.size(); ++i) {
-                if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
-                    mCallbacks.set(i, callback);
-                    return;
-                }
-            }
-            mCallbacks.add(callback);
-            mOptionsList.add(options);
-        }
-    }
-
-    private static class CallbackHandler extends Handler {
-        private final WeakReference<MediaBrowserServiceCallbackImpl> mCallbackImplRef;
-        private WeakReference<Messenger> mCallbacksMessengerRef;
-
-        CallbackHandler(MediaBrowserServiceCallbackImpl callbackImpl) {
-            super();
-            mCallbackImplRef = new WeakReference<>(callbackImpl);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (mCallbacksMessengerRef == null || mCallbacksMessengerRef.get() == null ||
-                    mCallbackImplRef.get() == null) {
-                return;
-            }
-            Bundle data = msg.getData();
-            data.setClassLoader(MediaSessionCompat.class.getClassLoader());
-            MediaBrowserServiceCallbackImpl serviceCallback = mCallbackImplRef.get();
-            Messenger callbacksMessenger = mCallbacksMessengerRef.get();
-            try {
-                switch (msg.what) {
-                    case SERVICE_MSG_ON_CONNECT:
-                        serviceCallback.onServiceConnected(callbacksMessenger,
-                                data.getString(DATA_MEDIA_ITEM_ID),
-                                (MediaSessionCompat.Token) data.getParcelable(
-                                        DATA_MEDIA_SESSION_TOKEN),
-                                data.getBundle(DATA_ROOT_HINTS));
-                        break;
-                    case SERVICE_MSG_ON_CONNECT_FAILED:
-                        serviceCallback.onConnectionFailed(callbacksMessenger);
-                        break;
-                    case SERVICE_MSG_ON_LOAD_CHILDREN:
-                        serviceCallback.onLoadChildren(callbacksMessenger,
-                                data.getString(DATA_MEDIA_ITEM_ID),
-                                data.getParcelableArrayList(DATA_MEDIA_ITEM_LIST),
-                                data.getBundle(DATA_OPTIONS));
-                        break;
-                    default:
-                        Log.w(TAG, "Unhandled message: " + msg
-                                + "\n  Client version: " + CLIENT_VERSION_CURRENT
-                                + "\n  Service version: " + msg.arg1);
-                }
-            } catch (BadParcelableException e) {
-                // Do not print the exception here, since it is already done by the Parcel class.
-                Log.e(TAG, "Could not unparcel the data.");
-                // If an error happened while connecting, disconnect from the service.
-                if (msg.what == SERVICE_MSG_ON_CONNECT) {
-                    serviceCallback.onConnectionFailed(callbacksMessenger);
-                }
-            }
-        }
-
-        void setCallbacksMessenger(Messenger callbacksMessenger) {
-            mCallbacksMessengerRef = new WeakReference<>(callbacksMessenger);
-        }
-    }
-
-    private static class ServiceBinderWrapper {
-        private Messenger mMessenger;
-        private Bundle mRootHints;
-
-        public ServiceBinderWrapper(IBinder target, Bundle rootHints) {
-            mMessenger = new Messenger(target);
-            mRootHints = rootHints;
-        }
-
-        void connect(Context context, Messenger callbacksMessenger)
-                throws RemoteException {
-            Bundle data = new Bundle();
-            data.putString(DATA_PACKAGE_NAME, context.getPackageName());
-            data.putBundle(DATA_ROOT_HINTS, mRootHints);
-            sendRequest(CLIENT_MSG_CONNECT, data, callbacksMessenger);
-        }
-
-        void disconnect(Messenger callbacksMessenger) throws RemoteException {
-            sendRequest(CLIENT_MSG_DISCONNECT, null, callbacksMessenger);
-        }
-
-        void addSubscription(String parentId, IBinder callbackToken, Bundle options,
-                Messenger callbacksMessenger)
-                throws RemoteException {
-            Bundle data = new Bundle();
-            data.putString(DATA_MEDIA_ITEM_ID, parentId);
-            BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
-            data.putBundle(DATA_OPTIONS, options);
-            sendRequest(CLIENT_MSG_ADD_SUBSCRIPTION, data, callbacksMessenger);
-        }
-
-        void removeSubscription(String parentId, IBinder callbackToken,
-                Messenger callbacksMessenger)
-                throws RemoteException {
-            Bundle data = new Bundle();
-            data.putString(DATA_MEDIA_ITEM_ID, parentId);
-            BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
-            sendRequest(CLIENT_MSG_REMOVE_SUBSCRIPTION, data, callbacksMessenger);
-        }
-
-        void getMediaItem(String mediaId, ResultReceiver receiver, Messenger callbacksMessenger)
-                throws RemoteException {
-            Bundle data = new Bundle();
-            data.putString(DATA_MEDIA_ITEM_ID, mediaId);
-            data.putParcelable(DATA_RESULT_RECEIVER, receiver);
-            sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, data, callbacksMessenger);
-        }
-
-        void registerCallbackMessenger(Messenger callbackMessenger) throws RemoteException {
-            Bundle data = new Bundle();
-            data.putBundle(DATA_ROOT_HINTS, mRootHints);
-            sendRequest(CLIENT_MSG_REGISTER_CALLBACK_MESSENGER, data, callbackMessenger);
-        }
-
-        void unregisterCallbackMessenger(Messenger callbackMessenger) throws RemoteException {
-            sendRequest(CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER, null, callbackMessenger);
-        }
-
-        void search(String query, Bundle extras, ResultReceiver receiver,
-                Messenger callbacksMessenger) throws RemoteException {
-            Bundle data = new Bundle();
-            data.putString(DATA_SEARCH_QUERY, query);
-            data.putBundle(DATA_SEARCH_EXTRAS, extras);
-            data.putParcelable(DATA_RESULT_RECEIVER, receiver);
-            sendRequest(CLIENT_MSG_SEARCH, data, callbacksMessenger);
-        }
-
-        void sendCustomAction(String action, Bundle extras, ResultReceiver receiver,
-                Messenger callbacksMessenger) throws RemoteException {
-            Bundle data = new Bundle();
-            data.putString(DATA_CUSTOM_ACTION, action);
-            data.putBundle(DATA_CUSTOM_ACTION_EXTRAS, extras);
-            data.putParcelable(DATA_RESULT_RECEIVER, receiver);
-            sendRequest(CLIENT_MSG_SEND_CUSTOM_ACTION, data, callbacksMessenger);
-        }
-
-        private void sendRequest(int what, Bundle data, Messenger cbMessenger)
-                throws RemoteException {
-            Message msg = Message.obtain();
-            msg.what = what;
-            msg.arg1 = CLIENT_VERSION_CURRENT;
-            msg.setData(data);
-            msg.replyTo = cbMessenger;
-            mMessenger.send(msg);
-        }
-    }
-
-    private  static class ItemReceiver extends ResultReceiver {
-        private final String mMediaId;
-        private final ItemCallback mCallback;
-
-        ItemReceiver(String mediaId, ItemCallback callback, Handler handler) {
-            super(handler);
-            mMediaId = mediaId;
-            mCallback = callback;
-        }
-
-        @Override
-        protected void onReceiveResult(int resultCode, Bundle resultData) {
-            if (resultData != null) {
-                resultData.setClassLoader(MediaBrowserCompat.class.getClassLoader());
-            }
-            if (resultCode != MediaBrowserServiceCompat.RESULT_OK || resultData == null
-                    || !resultData.containsKey(MediaBrowserServiceCompat.KEY_MEDIA_ITEM)) {
-                mCallback.onError(mMediaId);
-                return;
-            }
-            Parcelable item = resultData.getParcelable(MediaBrowserServiceCompat.KEY_MEDIA_ITEM);
-            if (item == null || item instanceof MediaItem) {
-                mCallback.onItemLoaded((MediaItem) item);
-            } else {
-                mCallback.onError(mMediaId);
-            }
-        }
-    }
-
-    private static class SearchResultReceiver extends ResultReceiver {
-        private final String mQuery;
-        private final Bundle mExtras;
-        private final SearchCallback mCallback;
-
-        SearchResultReceiver(String query, Bundle extras, SearchCallback callback,
-                Handler handler) {
-            super(handler);
-            mQuery = query;
-            mExtras = extras;
-            mCallback = callback;
-        }
-
-        @Override
-        protected void onReceiveResult(int resultCode, Bundle resultData) {
-            if (resultData != null) {
-                resultData.setClassLoader(MediaBrowserCompat.class.getClassLoader());
-            }
-            if (resultCode != MediaBrowserServiceCompat.RESULT_OK || resultData == null
-                    || !resultData.containsKey(MediaBrowserServiceCompat.KEY_SEARCH_RESULTS)) {
-                mCallback.onError(mQuery, mExtras);
-                return;
-            }
-            Parcelable[] items = resultData.getParcelableArray(
-                    MediaBrowserServiceCompat.KEY_SEARCH_RESULTS);
-            List<MediaItem> results = null;
-            if (items != null) {
-                results = new ArrayList<>();
-                for (Parcelable item : items) {
-                    results.add((MediaItem) item);
-                }
-            }
-            mCallback.onSearchResult(mQuery, mExtras, results);
-        }
-    }
-
-    private static class CustomActionResultReceiver extends ResultReceiver {
-        private final String mAction;
-        private final Bundle mExtras;
-        private final CustomActionCallback mCallback;
-
-        CustomActionResultReceiver(String action, Bundle extras, CustomActionCallback callback,
-                Handler handler) {
-            super(handler);
-            mAction = action;
-            mExtras = extras;
-            mCallback = callback;
-        }
-
-        @Override
-        protected void onReceiveResult(int resultCode, Bundle resultData) {
-            if (mCallback == null) {
-                return;
-            }
-            switch (resultCode) {
-                case MediaBrowserServiceCompat.RESULT_PROGRESS_UPDATE:
-                    mCallback.onProgressUpdate(mAction, mExtras, resultData);
-                    break;
-                case MediaBrowserServiceCompat.RESULT_OK:
-                    mCallback.onResult(mAction, mExtras, resultData);
-                    break;
-                case MediaBrowserServiceCompat.RESULT_ERROR:
-                    mCallback.onError(mAction, mExtras, resultData);
-                    break;
-                default:
-                    Log.w(TAG, "Unknown result code: " + resultCode + " (extras=" + mExtras
-                            + ", resultData=" + resultData + ")");
-                    break;
-            }
-        }
-    }
-}
diff --git a/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java b/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
index dba32ca..d3f8bbb 100644
--- a/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
+++ b/media/src/main/java/androidx/media/MediaBrowserCompatUtils.java
@@ -19,6 +19,7 @@
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat;
 
 import androidx.annotation.RestrictTo;
 
diff --git a/media/src/main/java/androidx/media/MediaBrowserProtocol.java b/media/src/main/java/androidx/media/MediaBrowserProtocol.java
index 2449435..251158f 100644
--- a/media/src/main/java/androidx/media/MediaBrowserProtocol.java
+++ b/media/src/main/java/androidx/media/MediaBrowserProtocol.java
@@ -15,10 +15,17 @@
  */
 package androidx.media;
 
+import android.support.v4.media.MediaBrowserCompat;
+
+import androidx.annotation.RestrictTo;
+
 /**
  * Defines the communication protocol for media browsers and media browser services.
+ *
+ * @hide
  */
-class MediaBrowserProtocol {
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class MediaBrowserProtocol {
 
     public static final String DATA_CALLBACK_TOKEN = "data_callback_token";
     public static final String DATA_CALLING_UID = "data_calling_uid";
diff --git a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
index 3b89fd3..8f18cfc 100644
--- a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
+++ b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
@@ -16,7 +16,7 @@
 
 package androidx.media;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_ADD_SUBSCRIPTION;
 import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_CONNECT;
 import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_DISCONNECT;
@@ -60,6 +60,7 @@
 import android.os.Messenger;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.session.IMediaSession;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.os.ResultReceiver;
@@ -130,7 +131,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP)
+    @RestrictTo(LIBRARY)
     public static final String KEY_MEDIA_ITEM = "media_item";
 
     /**
@@ -138,19 +139,32 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP)
+    @RestrictTo(LIBRARY)
     public static final String KEY_SEARCH_RESULTS = "search_results";
 
     static final int RESULT_FLAG_OPTION_NOT_HANDLED = 1 << 0;
     static final int RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED = 1 << 1;
     static final int RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED = 1 << 2;
 
-    static final int RESULT_ERROR = -1;
-    static final int RESULT_OK = 0;
-    static final int RESULT_PROGRESS_UPDATE = 1;
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final int RESULT_ERROR = -1;
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final int RESULT_OK = 0;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final int RESULT_PROGRESS_UPDATE = 1;
 
     /** @hide */
-    @RestrictTo(LIBRARY_GROUP)
+    @RestrictTo(LIBRARY)
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
             RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED, RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED })
diff --git a/media/src/main/java/androidx/media/session/MediaButtonReceiver.java b/media/src/main/java/androidx/media/session/MediaButtonReceiver.java
index 113cdfd..15b9e95 100644
--- a/media/src/main/java/androidx/media/session/MediaButtonReceiver.java
+++ b/media/src/main/java/androidx/media/session/MediaButtonReceiver.java
@@ -28,6 +28,8 @@
 import android.content.pm.ResolveInfo;
 import android.os.Build;
 import android.os.RemoteException;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.support.v4.media.session.PlaybackStateCompat.MediaKeyAction;
@@ -35,7 +37,6 @@
 import android.view.KeyEvent;
 
 import androidx.annotation.RestrictTo;
-import androidx.media.MediaBrowserCompat;
 import androidx.media.MediaBrowserServiceCompat;
 
 import java.util.List;
diff --git a/media/src/main/java/androidx/media/session/MediaControllerCompat.java b/media/src/main/java/androidx/media/session/MediaControllerCompat.java
deleted file mode 100644
index 5fcf429..0000000
--- a/media/src/main/java/androidx/media/session/MediaControllerCompat.java
+++ /dev/null
@@ -1,2494 +0,0 @@
-/*
- * Copyright 2018 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 androidx.media.session;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.session.MediaController;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.session.IMediaControllerCallback;
-import android.support.v4.media.session.IMediaSession;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.MediaSessionCompat.QueueItem;
-import android.support.v4.media.session.ParcelableVolumeInfo;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.core.app.BundleCompat;
-import androidx.core.app.SupportActivity;
-import androidx.media.MediaBrowserCompat;
-import androidx.media.VolumeProviderCompat;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * Allows an app to interact with an ongoing media session. Media buttons and
- * other commands can be sent to the session. A callback may be registered to
- * receive updates from the session, such as metadata and play state changes.
- * <p>
- * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
- * from the session owner.
- * <p>
- * MediaController objects are thread-safe.
- * <p>
- * This is a helper for accessing features in {@link android.media.session.MediaSession}
- * introduced after API level 4 in a backwards compatible fashion.
- * <p class="note">
- * If MediaControllerCompat is created with a {@link MediaSessionCompat.Token session token}
- * from another process, following methods will not work directly after the creation if the
- * {@link MediaSessionCompat.Token session token} is not passed through a
- * {@link MediaBrowserCompat}:
- * <ul>
- * <li>{@link #getPlaybackState()}.{@link PlaybackStateCompat#getExtras() getExtras()}</li>
- * <li>{@link #isCaptioningEnabled()}</li>
- * <li>{@link #getRepeatMode()}</li>
- * <li>{@link #getShuffleMode()}</li>
- * </ul></p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For information about building your media application, read the
- * <a href="{@docRoot}guide/topics/media-apps/index.html">Media Apps</a> developer guide.</p>
- * </div>
- */
-public final class MediaControllerCompat {
-    static final String TAG = "MediaControllerCompat";
-
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    public static final String COMMAND_GET_EXTRA_BINDER =
-            "android.support.v4.media.session.command.GET_EXTRA_BINDER";
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    public static final String COMMAND_ADD_QUEUE_ITEM =
-            "android.support.v4.media.session.command.ADD_QUEUE_ITEM";
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    public static final String COMMAND_ADD_QUEUE_ITEM_AT =
-            "android.support.v4.media.session.command.ADD_QUEUE_ITEM_AT";
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    public static final String COMMAND_REMOVE_QUEUE_ITEM =
-            "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM";
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    public static final String COMMAND_REMOVE_QUEUE_ITEM_AT =
-            "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM_AT";
-
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    public static final String COMMAND_ARGUMENT_MEDIA_DESCRIPTION =
-            "android.support.v4.media.session.command.ARGUMENT_MEDIA_DESCRIPTION";
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    public static final String COMMAND_ARGUMENT_INDEX =
-            "android.support.v4.media.session.command.ARGUMENT_INDEX";
-
-    private static class MediaControllerExtraData extends SupportActivity.ExtraData {
-        private final MediaControllerCompat mMediaController;
-
-        MediaControllerExtraData(MediaControllerCompat mediaController) {
-            mMediaController = mediaController;
-        }
-
-        MediaControllerCompat getMediaController() {
-            return mMediaController;
-        }
-    }
-
-    /**
-     * Sets a {@link MediaControllerCompat} in the {@code activity} for later retrieval via
-     * {@link #getMediaController(Activity)}.
-     *
-     * <p>This is compatible with {@link Activity#setMediaController(MediaController)}.
-     * If {@code activity} inherits {@link androidx.fragment.app.FragmentActivity}, the
-     * {@code mediaController} will be saved in the {@code activity}. In addition to that,
-     * on API 21 and later, {@link Activity#setMediaController(MediaController)} will be
-     * called.</p>
-     *
-     * @param activity The activity to set the {@code mediaController} in, must not be null.
-     * @param mediaController The controller for the session which should receive
-     *     media keys and volume changes on API 21 and later.
-     * @see #getMediaController(Activity)
-     * @see Activity#setMediaController(android.media.session.MediaController)
-     */
-    public static void setMediaController(@NonNull Activity activity,
-            MediaControllerCompat mediaController) {
-        if (activity instanceof SupportActivity) {
-            ((SupportActivity) activity).putExtraData(
-                    new MediaControllerExtraData(mediaController));
-        }
-        if (android.os.Build.VERSION.SDK_INT >= 21) {
-            Object controllerObj = null;
-            if (mediaController != null) {
-                Object sessionTokenObj = mediaController.getSessionToken().getToken();
-                controllerObj = MediaControllerCompatApi21.fromToken(activity, sessionTokenObj);
-            }
-            MediaControllerCompatApi21.setMediaController(activity, controllerObj);
-        }
-    }
-
-    /**
-     * Retrieves the {@link MediaControllerCompat} set in the activity by
-     * {@link #setMediaController(Activity, MediaControllerCompat)} for sending media key and volume
-     * events.
-     *
-     * <p>This is compatible with {@link Activity#getMediaController()}.</p>
-     *
-     * @param activity The activity to get the media controller from, must not be null.
-     * @return The controller which should receive events.
-     * @see #setMediaController(Activity, MediaControllerCompat)
-     */
-    public static MediaControllerCompat getMediaController(@NonNull Activity activity) {
-        if (activity instanceof SupportActivity) {
-            MediaControllerExtraData extraData =
-                    ((SupportActivity) activity).getExtraData(MediaControllerExtraData.class);
-            return extraData != null ? extraData.getMediaController() : null;
-        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
-            Object controllerObj = MediaControllerCompatApi21.getMediaController(activity);
-            if (controllerObj == null) {
-                return null;
-            }
-            Object sessionTokenObj = MediaControllerCompatApi21.getSessionToken(controllerObj);
-            try {
-                return new MediaControllerCompat(activity,
-                        MediaSessionCompat.Token.fromToken(sessionTokenObj));
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getMediaController.", e);
-            }
-        }
-        return null;
-    }
-
-    private static void validateCustomAction(String action, Bundle args) {
-        if (action == null) {
-            return;
-        }
-        switch(action) {
-            case MediaSessionCompat.ACTION_FOLLOW:
-            case MediaSessionCompat.ACTION_UNFOLLOW:
-                if (args == null
-                        || !args.containsKey(MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE)) {
-                    throw new IllegalArgumentException("An extra field "
-                            + MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE + " is required "
-                            + "for this action " + action + ".");
-                }
-                break;
-        }
-    }
-
-    private final MediaControllerImpl mImpl;
-    private final MediaSessionCompat.Token mToken;
-    // This set is used to keep references to registered callbacks to prevent them being GCed,
-    // since we only keep weak references for callbacks in this class and its inner classes.
-    private final HashSet<Callback> mRegisteredCallbacks = new HashSet<>();
-
-    /**
-     * Creates a media controller from a session.
-     *
-     * @param session The session to be controlled.
-     */
-    public MediaControllerCompat(Context context, @NonNull MediaSessionCompat session) {
-        if (session == null) {
-            throw new IllegalArgumentException("session must not be null");
-        }
-        mToken = session.getSessionToken();
-
-        if (android.os.Build.VERSION.SDK_INT >= 24) {
-            mImpl = new MediaControllerImplApi24(context, session);
-        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
-            mImpl = new MediaControllerImplApi23(context, session);
-        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
-            mImpl = new MediaControllerImplApi21(context, session);
-        } else {
-            mImpl = new MediaControllerImplBase(mToken);
-        }
-    }
-
-    /**
-     * Creates a media controller from a session token which may have
-     * been obtained from another process.
-     *
-     * @param sessionToken The token of the session to be controlled.
-     * @throws RemoteException if the session is not accessible.
-     */
-    public MediaControllerCompat(Context context, @NonNull MediaSessionCompat.Token sessionToken)
-            throws RemoteException {
-        if (sessionToken == null) {
-            throw new IllegalArgumentException("sessionToken must not be null");
-        }
-        mToken = sessionToken;
-
-        if (android.os.Build.VERSION.SDK_INT >= 24) {
-            mImpl = new MediaControllerImplApi24(context, sessionToken);
-        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
-            mImpl = new MediaControllerImplApi23(context, sessionToken);
-        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
-            mImpl = new MediaControllerImplApi21(context, sessionToken);
-        } else {
-            mImpl = new MediaControllerImplBase(mToken);
-        }
-    }
-
-    /**
-     * Gets a {@link TransportControls} instance for this session.
-     *
-     * @return A controls instance
-     */
-    public TransportControls getTransportControls() {
-        return mImpl.getTransportControls();
-    }
-
-    /**
-     * Sends the specified media button event to the session. Only media keys can
-     * be sent by this method, other keys will be ignored.
-     *
-     * @param keyEvent The media button event to dispatch.
-     * @return true if the event was sent to the session, false otherwise.
-     */
-    public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
-        if (keyEvent == null) {
-            throw new IllegalArgumentException("KeyEvent may not be null");
-        }
-        return mImpl.dispatchMediaButtonEvent(keyEvent);
-    }
-
-    /**
-     * Gets the current playback state for this session.
-     *
-     * <p>If the session is not ready, {@link PlaybackStateCompat#getExtras()} on the result of
-     * this method may return null. </p>
-     *
-     * @return The current PlaybackState or null
-     * @see #isSessionReady
-     * @see Callback#onSessionReady
-     */
-    public PlaybackStateCompat getPlaybackState() {
-        return mImpl.getPlaybackState();
-    }
-
-    /**
-     * Gets the current metadata for this session.
-     *
-     * @return The current MediaMetadata or null.
-     */
-    public MediaMetadataCompat getMetadata() {
-        return mImpl.getMetadata();
-    }
-
-    /**
-     * Gets the current play queue for this session if one is set. If you only
-     * care about the current item {@link #getMetadata()} should be used.
-     *
-     * @return The current play queue or null.
-     */
-    public List<QueueItem> getQueue() {
-        return mImpl.getQueue();
-    }
-
-    /**
-     * Adds a queue item from the given {@code description} at the end of the play queue
-     * of this session. Not all sessions may support this. To know whether the session supports
-     * this, get the session's flags with {@link #getFlags()} and check that the flag
-     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
-     *
-     * @param description The {@link MediaDescriptionCompat} for creating the
-     *            {@link MediaSessionCompat.QueueItem} to be inserted.
-     * @throws UnsupportedOperationException If this session doesn't support this.
-     * @see #getFlags()
-     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
-     */
-    public void addQueueItem(MediaDescriptionCompat description) {
-        mImpl.addQueueItem(description);
-    }
-
-    /**
-     * Adds a queue item from the given {@code description} at the specified position
-     * in the play queue of this session. Shifts the queue item currently at that position
-     * (if any) and any subsequent queue items to the right (adds one to their indices).
-     * Not all sessions may support this. To know whether the session supports this,
-     * get the session's flags with {@link #getFlags()} and check that the flag
-     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
-     *
-     * @param description The {@link MediaDescriptionCompat} for creating the
-     *            {@link MediaSessionCompat.QueueItem} to be inserted.
-     * @param index The index at which the created {@link MediaSessionCompat.QueueItem}
-     *            is to be inserted.
-     * @throws UnsupportedOperationException If this session doesn't support this.
-     * @see #getFlags()
-     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
-     */
-    public void addQueueItem(MediaDescriptionCompat description, int index) {
-        mImpl.addQueueItem(description, index);
-    }
-
-    /**
-     * Removes the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
-     * with the given {@link MediaDescriptionCompat description} in the play queue of the
-     * associated session. Not all sessions may support this. To know whether the session supports
-     * this, get the session's flags with {@link #getFlags()} and check that the flag
-     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
-     *
-     * @param description The {@link MediaDescriptionCompat} for denoting the
-     *            {@link MediaSessionCompat.QueueItem} to be removed.
-     * @throws UnsupportedOperationException If this session doesn't support this.
-     * @see #getFlags()
-     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
-     */
-    public void removeQueueItem(MediaDescriptionCompat description) {
-        mImpl.removeQueueItem(description);
-    }
-
-    /**
-     * Removes an queue item at the specified position in the play queue
-     * of this session. Not all sessions may support this. To know whether the session supports
-     * this, get the session's flags with {@link #getFlags()} and check that the flag
-     * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
-     *
-     * @param index The index of the element to be removed.
-     * @throws UnsupportedOperationException If this session doesn't support this.
-     * @see #getFlags()
-     * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
-     * @deprecated Use {@link #removeQueueItem(MediaDescriptionCompat)} instead.
-     */
-    @Deprecated
-    public void removeQueueItemAt(int index) {
-        List<QueueItem> queue = getQueue();
-        if (queue != null && index >= 0 && index < queue.size()) {
-            QueueItem item = queue.get(index);
-            if (item != null) {
-                removeQueueItem(item.getDescription());
-            }
-        }
-    }
-
-    /**
-     * Gets the queue title for this session.
-     */
-    public CharSequence getQueueTitle() {
-        return mImpl.getQueueTitle();
-    }
-
-    /**
-     * Gets the extras for this session.
-     */
-    public Bundle getExtras() {
-        return mImpl.getExtras();
-    }
-
-    /**
-     * Gets the rating type supported by the session. One of:
-     * <ul>
-     * <li>{@link RatingCompat#RATING_NONE}</li>
-     * <li>{@link RatingCompat#RATING_HEART}</li>
-     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
-     * <li>{@link RatingCompat#RATING_3_STARS}</li>
-     * <li>{@link RatingCompat#RATING_4_STARS}</li>
-     * <li>{@link RatingCompat#RATING_5_STARS}</li>
-     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
-     * </ul>
-     * <p>If the session is not ready, it will return {@link RatingCompat#RATING_NONE}.</p>
-     *
-     * @return The supported rating type, or {@link RatingCompat#RATING_NONE} if the value is not
-     *         set or the session is not ready.
-     * @see #isSessionReady
-     * @see Callback#onSessionReady
-     */
-    public int getRatingType() {
-        return mImpl.getRatingType();
-    }
-
-    /**
-     * Returns whether captioning is enabled for this session.
-     *
-     * <p>If the session is not ready, it will return a {@code false}.</p>
-     *
-     * @return {@code true} if captioning is enabled, {@code false} if disabled or not set.
-     * @see #isSessionReady
-     * @see Callback#onSessionReady
-     */
-    public boolean isCaptioningEnabled() {
-        return mImpl.isCaptioningEnabled();
-    }
-
-    /**
-     * Gets the repeat mode for this session.
-     *
-     * @return The latest repeat mode set to the session,
-     *         {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set, or
-     *         {@link PlaybackStateCompat#REPEAT_MODE_INVALID} if the session is not ready yet.
-     * @see #isSessionReady
-     * @see Callback#onSessionReady
-     */
-    public int getRepeatMode() {
-        return mImpl.getRepeatMode();
-    }
-
-    /**
-     * Gets the shuffle mode for this session.
-     *
-     * @return The latest shuffle mode set to the session, or
-     *         {@link PlaybackStateCompat#SHUFFLE_MODE_NONE} if disabled or not set, or
-     *         {@link PlaybackStateCompat#SHUFFLE_MODE_INVALID} if the session is not ready yet.
-     * @see #isSessionReady
-     * @see Callback#onSessionReady
-     */
-    public int getShuffleMode() {
-        return mImpl.getShuffleMode();
-    }
-
-    /**
-     * Gets the flags for this session. Flags are defined in
-     * {@link MediaSessionCompat}.
-     *
-     * @return The current set of flags for the session.
-     */
-    public long getFlags() {
-        return mImpl.getFlags();
-    }
-
-    /**
-     * Gets the current playback info for this session.
-     *
-     * @return The current playback info or null.
-     */
-    public PlaybackInfo getPlaybackInfo() {
-        return mImpl.getPlaybackInfo();
-    }
-
-    /**
-     * Gets an intent for launching UI associated with this session if one
-     * exists.
-     *
-     * @return A {@link PendingIntent} to launch UI or null.
-     */
-    public PendingIntent getSessionActivity() {
-        return mImpl.getSessionActivity();
-    }
-
-    /**
-     * Gets the token for the session this controller is connected to.
-     *
-     * @return The session's token.
-     */
-    public MediaSessionCompat.Token getSessionToken() {
-        return mToken;
-    }
-
-    /**
-     * Sets the volume of the output this session is playing on. The command will
-     * be ignored if it does not support
-     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
-     * {@link AudioManager} may be used to affect the handling.
-     *
-     * @see #getPlaybackInfo()
-     * @param value The value to set it to, between 0 and the reported max.
-     * @param flags Flags from {@link AudioManager} to include with the volume
-     *            request.
-     */
-    public void setVolumeTo(int value, int flags) {
-        mImpl.setVolumeTo(value, flags);
-    }
-
-    /**
-     * Adjusts the volume of the output this session is playing on. The direction
-     * must be one of {@link AudioManager#ADJUST_LOWER},
-     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
-     * The command will be ignored if the session does not support
-     * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
-     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
-     * {@link AudioManager} may be used to affect the handling.
-     *
-     * @see #getPlaybackInfo()
-     * @param direction The direction to adjust the volume in.
-     * @param flags Any flags to pass with the command.
-     */
-    public void adjustVolume(int direction, int flags) {
-        mImpl.adjustVolume(direction, flags);
-    }
-
-    /**
-     * Adds a callback to receive updates from the Session. Updates will be
-     * posted on the caller's thread.
-     *
-     * @param callback The callback object, must not be null.
-     */
-    public void registerCallback(@NonNull Callback callback) {
-        registerCallback(callback, null);
-    }
-
-    /**
-     * Adds a callback to receive updates from the session. Updates will be
-     * posted on the specified handler's thread.
-     *
-     * @param callback The callback object, must not be null.
-     * @param handler The handler to post updates on. If null the callers thread
-     *            will be used.
-     */
-    public void registerCallback(@NonNull Callback callback, Handler handler) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback must not be null");
-        }
-        if (handler == null) {
-            handler = new Handler();
-        }
-        callback.setHandler(handler);
-        mImpl.registerCallback(callback, handler);
-        mRegisteredCallbacks.add(callback);
-    }
-
-    /**
-     * Stops receiving updates on the specified callback. If an update has
-     * already been posted you may still receive it after calling this method.
-     *
-     * @param callback The callback to remove
-     */
-    public void unregisterCallback(@NonNull Callback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback must not be null");
-        }
-        try {
-            mRegisteredCallbacks.remove(callback);
-            mImpl.unregisterCallback(callback);
-        } finally {
-            callback.setHandler(null);
-        }
-    }
-
-    /**
-     * Sends a generic command to the session. It is up to the session creator
-     * to decide what commands and parameters they will support. As such,
-     * commands should only be sent to sessions that the controller owns.
-     *
-     * @param command The command to send
-     * @param params Any parameters to include with the command
-     * @param cb The callback to receive the result on
-     */
-    public void sendCommand(@NonNull String command, Bundle params, ResultReceiver cb) {
-        if (TextUtils.isEmpty(command)) {
-            throw new IllegalArgumentException("command must neither be null nor empty");
-        }
-        mImpl.sendCommand(command, params, cb);
-    }
-
-    /**
-     * Returns whether the session is ready or not.
-     *
-     * <p>If the session is not ready, following methods can work incorrectly.</p>
-     * <ul>
-     * <li>{@link #getPlaybackState()}</li>
-     * <li>{@link #getRatingType()}</li>
-     * <li>{@link #getRepeatMode()}</li>
-     * <li>{@link #getShuffleMode()}</li>
-     * <li>{@link #isCaptioningEnabled()}</li>
-     * </ul>
-     *
-     * @return true if the session is ready, false otherwise.
-     * @see Callback#onSessionReady()
-     */
-    public boolean isSessionReady() {
-        return mImpl.isSessionReady();
-    }
-
-    /**
-     * Gets the session owner's package name.
-     *
-     * @return The package name of of the session owner.
-     */
-    public String getPackageName() {
-        return mImpl.getPackageName();
-    }
-
-    /**
-     * Gets the underlying framework
-     * {@link android.media.session.MediaController} object.
-     * <p>
-     * This method is only supported on API 21+.
-     * </p>
-     *
-     * @return The underlying {@link android.media.session.MediaController}
-     *         object, or null if none.
-     */
-    public Object getMediaController() {
-        return mImpl.getMediaController();
-    }
-
-    /**
-     * Callback for receiving updates on from the session. A Callback can be
-     * registered using {@link #registerCallback}
-     */
-    public static abstract class Callback implements IBinder.DeathRecipient {
-        private final Object mCallbackObj;
-        MessageHandler mHandler;
-        boolean mHasExtraCallback;
-
-        public Callback() {
-            if (android.os.Build.VERSION.SDK_INT >= 21) {
-                mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21(this));
-            } else {
-                mCallbackObj = new StubCompat(this);
-            }
-        }
-
-        /**
-         * Override to handle the session being ready.
-         *
-         * @see MediaControllerCompat#isSessionReady
-         */
-        public void onSessionReady() {
-        }
-
-        /**
-         * Override to handle the session being destroyed. The session is no
-         * longer valid after this call and calls to it will be ignored.
-         */
-        public void onSessionDestroyed() {
-        }
-
-        /**
-         * Override to handle custom events sent by the session owner without a
-         * specified interface. Controllers should only handle these for
-         * sessions they own.
-         *
-         * @param event The event from the session.
-         * @param extras Optional parameters for the event.
-         */
-        public void onSessionEvent(String event, Bundle extras) {
-        }
-
-        /**
-         * Override to handle changes in playback state.
-         *
-         * @param state The new playback state of the session
-         */
-        public void onPlaybackStateChanged(PlaybackStateCompat state) {
-        }
-
-        /**
-         * Override to handle changes to the current metadata.
-         *
-         * @param metadata The current metadata for the session or null if none.
-         * @see MediaMetadataCompat
-         */
-        public void onMetadataChanged(MediaMetadataCompat metadata) {
-        }
-
-        /**
-         * Override to handle changes to items in the queue.
-         *
-         * @see MediaSessionCompat.QueueItem
-         * @param queue A list of items in the current play queue. It should
-         *            include the currently playing item as well as previous and
-         *            upcoming items if applicable.
-         */
-        public void onQueueChanged(List<QueueItem> queue) {
-        }
-
-        /**
-         * Override to handle changes to the queue title.
-         *
-         * @param title The title that should be displayed along with the play
-         *            queue such as "Now Playing". May be null if there is no
-         *            such title.
-         */
-        public void onQueueTitleChanged(CharSequence title) {
-        }
-
-        /**
-         * Override to handle changes to the {@link MediaSessionCompat} extras.
-         *
-         * @param extras The extras that can include other information
-         *            associated with the {@link MediaSessionCompat}.
-         */
-        public void onExtrasChanged(Bundle extras) {
-        }
-
-        /**
-         * Override to handle changes to the audio info.
-         *
-         * @param info The current audio info for this session.
-         */
-        public void onAudioInfoChanged(PlaybackInfo info) {
-        }
-
-        /**
-         * Override to handle changes to the captioning enabled status.
-         *
-         * @param enabled {@code true} if captioning is enabled, {@code false} otherwise.
-         */
-        public void onCaptioningEnabledChanged(boolean enabled) {
-        }
-
-        /**
-         * Override to handle changes to the repeat mode.
-         *
-         * @param repeatMode The repeat mode. It should be one of followings:
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
-         */
-        public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode int repeatMode) {
-        }
-
-        /**
-         * Override to handle changes to the shuffle mode.
-         *
-         * @param shuffleMode The shuffle mode. Must be one of the followings:
-         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
-         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
-         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
-         */
-        public void onShuffleModeChanged(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
-        }
-
-        @Override
-        public void binderDied() {
-            onSessionDestroyed();
-        }
-
-        /**
-         * Set the handler to use for callbacks.
-         */
-        void setHandler(Handler handler) {
-            if (handler == null) {
-                if (mHandler != null) {
-                    mHandler.mRegistered = false;
-                    mHandler.removeCallbacksAndMessages(null);
-                    mHandler = null;
-                }
-            } else {
-                mHandler = new MessageHandler(handler.getLooper());
-                mHandler.mRegistered = true;
-            }
-        }
-
-        void postToHandler(int what, Object obj, Bundle data) {
-            if (mHandler != null) {
-                Message msg = mHandler.obtainMessage(what, obj);
-                msg.setData(data);
-                msg.sendToTarget();
-            }
-        }
-
-        private static class StubApi21 implements MediaControllerCompatApi21.Callback {
-            private final WeakReference<MediaControllerCompat.Callback> mCallback;
-
-            StubApi21(MediaControllerCompat.Callback callback) {
-                mCallback = new WeakReference<>(callback);
-            }
-
-            @Override
-            public void onSessionDestroyed() {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.onSessionDestroyed();
-                }
-            }
-
-            @Override
-            public void onSessionEvent(String event, Bundle extras) {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    if (callback.mHasExtraCallback && android.os.Build.VERSION.SDK_INT < 23) {
-                        // Ignore. ExtraCallback will handle this.
-                    } else {
-                        callback.onSessionEvent(event, extras);
-                    }
-                }
-            }
-
-            @Override
-            public void onPlaybackStateChanged(Object stateObj) {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    if (callback.mHasExtraCallback) {
-                        // Ignore. ExtraCallback will handle this.
-                    } else {
-                        callback.onPlaybackStateChanged(
-                                PlaybackStateCompat.fromPlaybackState(stateObj));
-                    }
-                }
-            }
-
-            @Override
-            public void onMetadataChanged(Object metadataObj) {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
-                }
-            }
-
-            @Override
-            public void onQueueChanged(List<?> queue) {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.onQueueChanged(QueueItem.fromQueueItemList(queue));
-                }
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.onQueueTitleChanged(title);
-                }
-            }
-
-            @Override
-            public void onExtrasChanged(Bundle extras) {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.onExtrasChanged(extras);
-                }
-            }
-
-            @Override
-            public void onAudioInfoChanged(
-                    int type, int stream, int control, int max, int current) {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.onAudioInfoChanged(
-                            new PlaybackInfo(type, stream, control, max, current));
-                }
-            }
-        }
-
-        private static class StubCompat extends IMediaControllerCallback.Stub {
-            private final WeakReference<MediaControllerCompat.Callback> mCallback;
-
-            StubCompat(MediaControllerCompat.Callback callback) {
-                mCallback = new WeakReference<>(callback);
-            }
-
-            @Override
-            public void onEvent(String event, Bundle extras) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_EVENT, event, extras);
-                }
-            }
-
-            @Override
-            public void onSessionDestroyed() throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_DESTROYED, null, null);
-                }
-            }
-
-            @Override
-            public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
-                }
-            }
-
-            @Override
-            public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
-                }
-            }
-
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
-                }
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
-                }
-            }
-
-            @Override
-            public void onCaptioningEnabledChanged(boolean enabled) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(
-                            MessageHandler.MSG_UPDATE_CAPTIONING_ENABLED, enabled, null);
-                }
-            }
-
-            @Override
-            public void onRepeatModeChanged(int repeatMode) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null);
-                }
-            }
-
-            @Override
-            public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException {
-                // Do nothing.
-            }
-
-            @Override
-            public void onShuffleModeChanged(int shuffleMode) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(
-                            MessageHandler.MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null);
-                }
-            }
-
-            @Override
-            public void onExtrasChanged(Bundle extras) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
-                }
-            }
-
-            @Override
-            public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    PlaybackInfo pi = null;
-                    if (info != null) {
-                        pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
-                                info.maxVolume, info.currentVolume);
-                    }
-                    callback.postToHandler(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
-                }
-            }
-
-            @Override
-            public void onSessionReady() throws RemoteException {
-                MediaControllerCompat.Callback callback = mCallback.get();
-                if (callback != null) {
-                    callback.postToHandler(MessageHandler.MSG_SESSION_READY, null, null);
-                }
-            }
-        }
-
-        private class MessageHandler extends Handler {
-            private static final int MSG_EVENT = 1;
-            private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
-            private static final int MSG_UPDATE_METADATA = 3;
-            private static final int MSG_UPDATE_VOLUME = 4;
-            private static final int MSG_UPDATE_QUEUE = 5;
-            private static final int MSG_UPDATE_QUEUE_TITLE = 6;
-            private static final int MSG_UPDATE_EXTRAS = 7;
-            private static final int MSG_DESTROYED = 8;
-            private static final int MSG_UPDATE_REPEAT_MODE = 9;
-            private static final int MSG_UPDATE_CAPTIONING_ENABLED = 11;
-            private static final int MSG_UPDATE_SHUFFLE_MODE = 12;
-            private static final int MSG_SESSION_READY = 13;
-
-            boolean mRegistered = false;
-
-            MessageHandler(Looper looper) {
-                super(looper);
-            }
-
-            @Override
-            public void handleMessage(Message msg) {
-                if (!mRegistered) {
-                    return;
-                }
-                switch (msg.what) {
-                    case MSG_EVENT:
-                        onSessionEvent((String) msg.obj, msg.getData());
-                        break;
-                    case MSG_UPDATE_PLAYBACK_STATE:
-                        onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
-                        break;
-                    case MSG_UPDATE_METADATA:
-                        onMetadataChanged((MediaMetadataCompat) msg.obj);
-                        break;
-                    case MSG_UPDATE_QUEUE:
-                        onQueueChanged((List<QueueItem>) msg.obj);
-                        break;
-                    case MSG_UPDATE_QUEUE_TITLE:
-                        onQueueTitleChanged((CharSequence) msg.obj);
-                        break;
-                    case MSG_UPDATE_CAPTIONING_ENABLED:
-                        onCaptioningEnabledChanged((boolean) msg.obj);
-                        break;
-                    case MSG_UPDATE_REPEAT_MODE:
-                        onRepeatModeChanged((int) msg.obj);
-                        break;
-                    case MSG_UPDATE_SHUFFLE_MODE:
-                        onShuffleModeChanged((int) msg.obj);
-                        break;
-                    case MSG_UPDATE_EXTRAS:
-                        onExtrasChanged((Bundle) msg.obj);
-                        break;
-                    case MSG_UPDATE_VOLUME:
-                        onAudioInfoChanged((PlaybackInfo) msg.obj);
-                        break;
-                    case MSG_DESTROYED:
-                        onSessionDestroyed();
-                        break;
-                    case MSG_SESSION_READY:
-                        onSessionReady();
-                        break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Interface for controlling media playback on a session. This allows an app
-     * to send media transport commands to the session.
-     */
-    public static abstract class TransportControls {
-        /**
-         * Used as an integer extra field in {@link #playFromMediaId(String, Bundle)} or
-         * {@link #prepareFromMediaId(String, Bundle)} to indicate the stream type to be used by the
-         * media player when playing or preparing the specified media id. See {@link AudioManager}
-         * for a list of stream types.
-         */
-        public static final String EXTRA_LEGACY_STREAM_TYPE =
-                "android.media.session.extra.LEGACY_STREAM_TYPE";
-
-        TransportControls() {
-        }
-
-        /**
-         * Request that the player prepare for playback. This can decrease the time it takes to
-         * start playback when a play command is received. Preparation is not required. You can
-         * call {@link #play} without calling this method beforehand.
-         */
-        public abstract void prepare();
-
-        /**
-         * Request that the player prepare playback for a specific media id. This can decrease the
-         * time it takes to start playback when a play command is received. Preparation is not
-         * required. You can call {@link #playFromMediaId} without calling this method beforehand.
-         *
-         * @param mediaId The id of the requested media.
-         * @param extras Optional extras that can include extra information about the media item
-         *               to be prepared.
-         */
-        public abstract void prepareFromMediaId(String mediaId, Bundle extras);
-
-        /**
-         * Request that the player prepare playback for a specific search query. This can decrease
-         * the time it takes to start playback when a play command is received. An empty or null
-         * query should be treated as a request to prepare any music. Preparation is not required.
-         * You can call {@link #playFromSearch} without calling this method beforehand.
-         *
-         * @param query The search query.
-         * @param extras Optional extras that can include extra information
-         *               about the query.
-         */
-        public abstract void prepareFromSearch(String query, Bundle extras);
-
-        /**
-         * Request that the player prepare playback for a specific {@link Uri}. This can decrease
-         * the time it takes to start playback when a play command is received. Preparation is not
-         * required. You can call {@link #playFromUri} without calling this method beforehand.
-         *
-         * @param uri The URI of the requested media.
-         * @param extras Optional extras that can include extra information about the media item
-         *               to be prepared.
-         */
-        public abstract void prepareFromUri(Uri uri, Bundle extras);
-
-        /**
-         * Request that the player start its playback at its current position.
-         */
-        public abstract void play();
-
-        /**
-         * Request that the player start playback for a specific {@link Uri}.
-         *
-         * @param mediaId The uri of the requested media.
-         * @param extras Optional extras that can include extra information
-         *            about the media item to be played.
-         */
-        public abstract void playFromMediaId(String mediaId, Bundle extras);
-
-        /**
-         * Request that the player start playback for a specific search query.
-         * An empty or null query should be treated as a request to play any
-         * music.
-         *
-         * @param query The search query.
-         * @param extras Optional extras that can include extra information
-         *            about the query.
-         */
-        public abstract void playFromSearch(String query, Bundle extras);
-
-        /**
-         * Request that the player start playback for a specific {@link Uri}.
-         *
-         * @param uri  The URI of the requested media.
-         * @param extras Optional extras that can include extra information about the media item
-         *               to be played.
-         */
-        public abstract void playFromUri(Uri uri, Bundle extras);
-
-        /**
-         * Plays an item with a specific id in the play queue. If you specify an
-         * id that is not in the play queue, the behavior is undefined.
-         */
-        public abstract void skipToQueueItem(long id);
-
-        /**
-         * Request that the player pause its playback and stay at its current
-         * position.
-         */
-        public abstract void pause();
-
-        /**
-         * Request that the player stop its playback; it may clear its state in
-         * whatever way is appropriate.
-         */
-        public abstract void stop();
-
-        /**
-         * Moves to a new location in the media stream.
-         *
-         * @param pos Position to move to, in milliseconds.
-         */
-        public abstract void seekTo(long pos);
-
-        /**
-         * Starts fast forwarding. If playback is already fast forwarding this
-         * may increase the rate.
-         */
-        public abstract void fastForward();
-
-        /**
-         * Skips to the next item.
-         */
-        public abstract void skipToNext();
-
-        /**
-         * Starts rewinding. If playback is already rewinding this may increase
-         * the rate.
-         */
-        public abstract void rewind();
-
-        /**
-         * Skips to the previous item.
-         */
-        public abstract void skipToPrevious();
-
-        /**
-         * Rates the current content. This will cause the rating to be set for
-         * the current user. The rating type of the given {@link RatingCompat} must match the type
-         * returned by {@link #getRatingType()}.
-         *
-         * @param rating The rating to set for the current content
-         */
-        public abstract void setRating(RatingCompat rating);
-
-        /**
-         * Rates a media item. This will cause the rating to be set for
-         * the specific media item. The rating type of the given {@link RatingCompat} must match
-         * the type returned by {@link #getRatingType()}.
-         *
-         * @param rating The rating to set for the media item.
-         * @param extras Optional arguments that can include information about the media item
-         *               to be rated.
-         *
-         * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE
-         * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE_VALUE
-         */
-        public abstract void setRating(RatingCompat rating, Bundle extras);
-
-        /**
-         * Enables/disables captioning for this session.
-         *
-         * @param enabled {@code true} to enable captioning, {@code false} to disable.
-         */
-        public abstract void setCaptioningEnabled(boolean enabled);
-
-        /**
-         * Sets the repeat mode for this session.
-         *
-         * @param repeatMode The repeat mode. Must be one of the followings:
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
-         *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
-         */
-        public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
-
-        /**
-         * Sets the shuffle mode for this session.
-         *
-         * @param shuffleMode The shuffle mode. Must be one of the followings:
-         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
-         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
-         *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
-         */
-        public abstract void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
-
-        /**
-         * Sends a custom action for the {@link MediaSessionCompat} to perform.
-         *
-         * @param customAction The action to perform.
-         * @param args Optional arguments to supply to the
-         *            {@link MediaSessionCompat} for this custom action.
-         */
-        public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
-                Bundle args);
-
-        /**
-         * Sends the id and args from a custom action for the
-         * {@link MediaSessionCompat} to perform.
-         *
-         * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
-         *      Bundle args)
-         * @see MediaSessionCompat#ACTION_FLAG_AS_INAPPROPRIATE
-         * @see MediaSessionCompat#ACTION_SKIP_AD
-         * @see MediaSessionCompat#ACTION_FOLLOW
-         * @see MediaSessionCompat#ACTION_UNFOLLOW
-         * @param action The action identifier of the
-         *            {@link PlaybackStateCompat.CustomAction} as specified by
-         *            the {@link MediaSessionCompat}.
-         * @param args Optional arguments to supply to the
-         *            {@link MediaSessionCompat} for this custom action.
-         */
-        public abstract void sendCustomAction(String action, Bundle args);
-    }
-
-    /**
-     * Holds information about the way volume is handled for this session.
-     */
-    public static final class PlaybackInfo {
-        /**
-         * The session uses local playback.
-         */
-        public static final int PLAYBACK_TYPE_LOCAL = 1;
-        /**
-         * The session uses remote playback.
-         */
-        public static final int PLAYBACK_TYPE_REMOTE = 2;
-
-        private final int mPlaybackType;
-        // TODO update audio stream with AudioAttributes support version
-        private final int mAudioStream;
-        private final int mVolumeControl;
-        private final int mMaxVolume;
-        private final int mCurrentVolume;
-
-        PlaybackInfo(int type, int stream, int control, int max, int current) {
-            mPlaybackType = type;
-            mAudioStream = stream;
-            mVolumeControl = control;
-            mMaxVolume = max;
-            mCurrentVolume = current;
-        }
-
-        /**
-         * Gets the type of volume handling, either local or remote. One of:
-         * <ul>
-         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
-         * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
-         * </ul>
-         *
-         * @return The type of volume handling this session is using.
-         */
-        public int getPlaybackType() {
-            return mPlaybackType;
-        }
-
-        /**
-         * Gets the stream this is currently controlling volume on. When the volume
-         * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
-         * have meaning and should be ignored.
-         *
-         * @return The stream this session is playing on.
-         */
-        public int getAudioStream() {
-            // TODO switch to AudioAttributesCompat when it is added.
-            return mAudioStream;
-        }
-
-        /**
-         * Gets the type of volume control that can be used. One of:
-         * <ul>
-         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
-         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
-         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
-         * </ul>
-         *
-         * @return The type of volume control that may be used with this
-         *         session.
-         */
-        public int getVolumeControl() {
-            return mVolumeControl;
-        }
-
-        /**
-         * Gets the maximum volume that may be set for this session.
-         *
-         * @return The maximum allowed volume where this session is playing.
-         */
-        public int getMaxVolume() {
-            return mMaxVolume;
-        }
-
-        /**
-         * Gets the current volume for this session.
-         *
-         * @return The current volume where this session is playing.
-         */
-        public int getCurrentVolume() {
-            return mCurrentVolume;
-        }
-    }
-
-    interface MediaControllerImpl {
-        void registerCallback(Callback callback, Handler handler);
-
-        void unregisterCallback(Callback callback);
-        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
-        TransportControls getTransportControls();
-        PlaybackStateCompat getPlaybackState();
-        MediaMetadataCompat getMetadata();
-
-        List<QueueItem> getQueue();
-        void addQueueItem(MediaDescriptionCompat description);
-        void addQueueItem(MediaDescriptionCompat description, int index);
-        void removeQueueItem(MediaDescriptionCompat description);
-        CharSequence getQueueTitle();
-        Bundle getExtras();
-        int getRatingType();
-        boolean isCaptioningEnabled();
-        int getRepeatMode();
-        int getShuffleMode();
-        long getFlags();
-        PlaybackInfo getPlaybackInfo();
-        PendingIntent getSessionActivity();
-
-        void setVolumeTo(int value, int flags);
-        void adjustVolume(int direction, int flags);
-        void sendCommand(String command, Bundle params, ResultReceiver cb);
-
-        boolean isSessionReady();
-        String getPackageName();
-        Object getMediaController();
-    }
-
-    static class MediaControllerImplBase implements MediaControllerImpl {
-        private IMediaSession mBinder;
-        private TransportControls mTransportControls;
-
-        public MediaControllerImplBase(MediaSessionCompat.Token token) {
-            mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
-        }
-
-        @Override
-        public void registerCallback(Callback callback, Handler handler) {
-            if (callback == null) {
-                throw new IllegalArgumentException("callback may not be null.");
-            }
-            try {
-                mBinder.asBinder().linkToDeath(callback, 0);
-                mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in registerCallback.", e);
-                callback.onSessionDestroyed();
-            }
-        }
-
-        @Override
-        public void unregisterCallback(Callback callback) {
-            if (callback == null) {
-                throw new IllegalArgumentException("callback may not be null.");
-            }
-            try {
-                mBinder.unregisterCallbackListener(
-                        (IMediaControllerCallback) callback.mCallbackObj);
-                mBinder.asBinder().unlinkToDeath(callback, 0);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in unregisterCallback.", e);
-            }
-        }
-
-        @Override
-        public boolean dispatchMediaButtonEvent(KeyEvent event) {
-            if (event == null) {
-                throw new IllegalArgumentException("event may not be null.");
-            }
-            try {
-                mBinder.sendMediaButton(event);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in dispatchMediaButtonEvent.", e);
-            }
-            return false;
-        }
-
-        @Override
-        public TransportControls getTransportControls() {
-            if (mTransportControls == null) {
-                mTransportControls = new TransportControlsBase(mBinder);
-            }
-
-            return mTransportControls;
-        }
-
-        @Override
-        public PlaybackStateCompat getPlaybackState() {
-            try {
-                return mBinder.getPlaybackState();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getPlaybackState.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public MediaMetadataCompat getMetadata() {
-            try {
-                return mBinder.getMetadata();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getMetadata.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public List<QueueItem> getQueue() {
-            try {
-                return mBinder.getQueue();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getQueue.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public void addQueueItem(MediaDescriptionCompat description) {
-            try {
-                long flags = mBinder.getFlags();
-                if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
-                    throw new UnsupportedOperationException(
-                            "This session doesn't support queue management operations");
-                }
-                mBinder.addQueueItem(description);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in addQueueItem.", e);
-            }
-        }
-
-        @Override
-        public void addQueueItem(MediaDescriptionCompat description, int index) {
-            try {
-                long flags = mBinder.getFlags();
-                if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
-                    throw new UnsupportedOperationException(
-                            "This session doesn't support queue management operations");
-                }
-                mBinder.addQueueItemAt(description, index);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in addQueueItemAt.", e);
-            }
-        }
-
-        @Override
-        public void removeQueueItem(MediaDescriptionCompat description) {
-            try {
-                long flags = mBinder.getFlags();
-                if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
-                    throw new UnsupportedOperationException(
-                            "This session doesn't support queue management operations");
-                }
-                mBinder.removeQueueItem(description);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in removeQueueItem.", e);
-            }
-        }
-
-        @Override
-        public CharSequence getQueueTitle() {
-            try {
-                return mBinder.getQueueTitle();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getQueueTitle.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public Bundle getExtras() {
-            try {
-                return mBinder.getExtras();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getExtras.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public int getRatingType() {
-            try {
-                return mBinder.getRatingType();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getRatingType.", e);
-            }
-            return 0;
-        }
-
-        @Override
-        public boolean isCaptioningEnabled() {
-            try {
-                return mBinder.isCaptioningEnabled();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
-            }
-            return false;
-        }
-
-        @Override
-        public int getRepeatMode() {
-            try {
-                return mBinder.getRepeatMode();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getRepeatMode.", e);
-            }
-            return PlaybackStateCompat.REPEAT_MODE_INVALID;
-        }
-
-        @Override
-        public int getShuffleMode() {
-            try {
-                return mBinder.getShuffleMode();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getShuffleMode.", e);
-            }
-            return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
-        }
-
-        @Override
-        public long getFlags() {
-            try {
-                return mBinder.getFlags();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getFlags.", e);
-            }
-            return 0;
-        }
-
-        @Override
-        public PlaybackInfo getPlaybackInfo() {
-            try {
-                ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
-                PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
-                        info.controlType, info.maxVolume, info.currentVolume);
-                return pi;
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getPlaybackInfo.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public PendingIntent getSessionActivity() {
-            try {
-                return mBinder.getLaunchPendingIntent();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getSessionActivity.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public void setVolumeTo(int value, int flags) {
-            try {
-                mBinder.setVolumeTo(value, flags, null);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in setVolumeTo.", e);
-            }
-        }
-
-        @Override
-        public void adjustVolume(int direction, int flags) {
-            try {
-                mBinder.adjustVolume(direction, flags, null);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in adjustVolume.", e);
-            }
-        }
-
-        @Override
-        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
-            try {
-                mBinder.sendCommand(command, params,
-                        new MediaSessionCompat.ResultReceiverWrapper(cb));
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in sendCommand.", e);
-            }
-        }
-
-        @Override
-        public boolean isSessionReady() {
-            return true;
-        }
-
-        @Override
-        public String getPackageName() {
-            try {
-                return mBinder.getPackageName();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in getPackageName.", e);
-            }
-            return null;
-        }
-
-        @Override
-        public Object getMediaController() {
-            return null;
-        }
-    }
-
-    static class TransportControlsBase extends TransportControls {
-        private IMediaSession mBinder;
-
-        public TransportControlsBase(IMediaSession binder) {
-            mBinder = binder;
-        }
-
-        @Override
-        public void prepare() {
-            try {
-                mBinder.prepare();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in prepare.", e);
-            }
-        }
-
-        @Override
-        public void prepareFromMediaId(String mediaId, Bundle extras) {
-            try {
-                mBinder.prepareFromMediaId(mediaId, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in prepareFromMediaId.", e);
-            }
-        }
-
-        @Override
-        public void prepareFromSearch(String query, Bundle extras) {
-            try {
-                mBinder.prepareFromSearch(query, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in prepareFromSearch.", e);
-            }
-        }
-
-        @Override
-        public void prepareFromUri(Uri uri, Bundle extras) {
-            try {
-                mBinder.prepareFromUri(uri, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in prepareFromUri.", e);
-            }
-        }
-
-        @Override
-        public void play() {
-            try {
-                mBinder.play();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in play.", e);
-            }
-        }
-
-        @Override
-        public void playFromMediaId(String mediaId, Bundle extras) {
-            try {
-                mBinder.playFromMediaId(mediaId, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in playFromMediaId.", e);
-            }
-        }
-
-        @Override
-        public void playFromSearch(String query, Bundle extras) {
-            try {
-                mBinder.playFromSearch(query, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in playFromSearch.", e);
-            }
-        }
-
-        @Override
-        public void playFromUri(Uri uri, Bundle extras) {
-            try {
-                mBinder.playFromUri(uri, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in playFromUri.", e);
-            }
-        }
-
-        @Override
-        public void skipToQueueItem(long id) {
-            try {
-                mBinder.skipToQueueItem(id);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in skipToQueueItem.", e);
-            }
-        }
-
-        @Override
-        public void pause() {
-            try {
-                mBinder.pause();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in pause.", e);
-            }
-        }
-
-        @Override
-        public void stop() {
-            try {
-                mBinder.stop();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in stop.", e);
-            }
-        }
-
-        @Override
-        public void seekTo(long pos) {
-            try {
-                mBinder.seekTo(pos);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in seekTo.", e);
-            }
-        }
-
-        @Override
-        public void fastForward() {
-            try {
-                mBinder.fastForward();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in fastForward.", e);
-            }
-        }
-
-        @Override
-        public void skipToNext() {
-            try {
-                mBinder.next();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in skipToNext.", e);
-            }
-        }
-
-        @Override
-        public void rewind() {
-            try {
-                mBinder.rewind();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in rewind.", e);
-            }
-        }
-
-        @Override
-        public void skipToPrevious() {
-            try {
-                mBinder.previous();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in skipToPrevious.", e);
-            }
-        }
-
-        @Override
-        public void setRating(RatingCompat rating) {
-            try {
-                mBinder.rate(rating);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in setRating.", e);
-            }
-        }
-
-        @Override
-        public void setRating(RatingCompat rating, Bundle extras) {
-            try {
-                mBinder.rateWithExtras(rating, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in setRating.", e);
-            }
-        }
-
-        @Override
-        public void setCaptioningEnabled(boolean enabled) {
-            try {
-                mBinder.setCaptioningEnabled(enabled);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in setCaptioningEnabled.", e);
-            }
-        }
-
-        @Override
-        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
-            try {
-                mBinder.setRepeatMode(repeatMode);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in setRepeatMode.", e);
-            }
-        }
-
-        @Override
-        public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
-            try {
-                mBinder.setShuffleMode(shuffleMode);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in setShuffleMode.", e);
-            }
-        }
-
-        @Override
-        public void sendCustomAction(CustomAction customAction, Bundle args) {
-            sendCustomAction(customAction.getAction(), args);
-        }
-
-        @Override
-        public void sendCustomAction(String action, Bundle args) {
-            validateCustomAction(action, args);
-            try {
-                mBinder.sendCustomAction(action, args);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Dead object in sendCustomAction.", e);
-            }
-        }
-    }
-
-    @RequiresApi(21)
-    static class MediaControllerImplApi21 implements MediaControllerImpl {
-        protected final Object mControllerObj;
-
-        private final List<Callback> mPendingCallbacks = new ArrayList<>();
-
-        // Extra binder is used for applying the framework change of new APIs and bug fixes
-        // after API 21.
-        private IMediaSession mExtraBinder;
-        private HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>();
-
-        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
-            mControllerObj = MediaControllerCompatApi21.fromToken(context,
-                    session.getSessionToken().getToken());
-            mExtraBinder = session.getSessionToken().getExtraBinder();
-            if (mExtraBinder == null) {
-                requestExtraBinder();
-            }
-        }
-
-        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
-                throws RemoteException {
-            mControllerObj = MediaControllerCompatApi21.fromToken(context,
-                    sessionToken.getToken());
-            if (mControllerObj == null) throw new RemoteException();
-            mExtraBinder = sessionToken.getExtraBinder();
-            if (mExtraBinder == null) {
-                requestExtraBinder();
-            }
-        }
-
-        @Override
-        public final void registerCallback(Callback callback, Handler handler) {
-            MediaControllerCompatApi21.registerCallback(
-                    mControllerObj, callback.mCallbackObj, handler);
-            if (mExtraBinder != null) {
-                ExtraCallback extraCallback = new ExtraCallback(callback);
-                mCallbackMap.put(callback, extraCallback);
-                callback.mHasExtraCallback = true;
-                try {
-                    mExtraBinder.registerCallbackListener(extraCallback);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in registerCallback.", e);
-                }
-            } else {
-                synchronized (mPendingCallbacks) {
-                    callback.mHasExtraCallback = false;
-                    mPendingCallbacks.add(callback);
-                }
-            }
-        }
-
-        @Override
-        public final void unregisterCallback(Callback callback) {
-            MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
-            if (mExtraBinder != null) {
-                try {
-                    ExtraCallback extraCallback = mCallbackMap.remove(callback);
-                    if (extraCallback != null) {
-                        callback.mHasExtraCallback = false;
-                        mExtraBinder.unregisterCallbackListener(extraCallback);
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in unregisterCallback.", e);
-                }
-            } else {
-                synchronized (mPendingCallbacks) {
-                    mPendingCallbacks.remove(callback);
-                }
-            }
-        }
-
-        @Override
-        public boolean dispatchMediaButtonEvent(KeyEvent event) {
-            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
-        }
-
-        @Override
-        public TransportControls getTransportControls() {
-            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
-            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
-        }
-
-        @Override
-        public PlaybackStateCompat getPlaybackState() {
-            if (mExtraBinder != null) {
-                try {
-                    return mExtraBinder.getPlaybackState();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in getPlaybackState.", e);
-                }
-            }
-            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
-            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
-        }
-
-        @Override
-        public MediaMetadataCompat getMetadata() {
-            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
-            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
-        }
-
-        @Override
-        public List<QueueItem> getQueue() {
-            List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
-            return queueObjs != null ? QueueItem.fromQueueItemList(queueObjs) : null;
-        }
-
-        @Override
-        public void addQueueItem(MediaDescriptionCompat description) {
-            long flags = getFlags();
-            if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
-                throw new UnsupportedOperationException(
-                        "This session doesn't support queue management operations");
-            }
-            Bundle params = new Bundle();
-            params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
-            sendCommand(COMMAND_ADD_QUEUE_ITEM, params, null);
-        }
-
-        @Override
-        public void addQueueItem(MediaDescriptionCompat description, int index) {
-            long flags = getFlags();
-            if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
-                throw new UnsupportedOperationException(
-                        "This session doesn't support queue management operations");
-            }
-            Bundle params = new Bundle();
-            params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
-            params.putInt(COMMAND_ARGUMENT_INDEX, index);
-            sendCommand(COMMAND_ADD_QUEUE_ITEM_AT, params, null);
-        }
-
-        @Override
-        public void removeQueueItem(MediaDescriptionCompat description) {
-            long flags = getFlags();
-            if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
-                throw new UnsupportedOperationException(
-                        "This session doesn't support queue management operations");
-            }
-            Bundle params = new Bundle();
-            params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
-            sendCommand(COMMAND_REMOVE_QUEUE_ITEM, params, null);
-        }
-
-        @Override
-        public CharSequence getQueueTitle() {
-            return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
-        }
-
-        @Override
-        public Bundle getExtras() {
-            return MediaControllerCompatApi21.getExtras(mControllerObj);
-        }
-
-        @Override
-        public int getRatingType() {
-            if (android.os.Build.VERSION.SDK_INT < 22 && mExtraBinder != null) {
-                try {
-                    return mExtraBinder.getRatingType();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in getRatingType.", e);
-                }
-            }
-            return MediaControllerCompatApi21.getRatingType(mControllerObj);
-        }
-
-        @Override
-        public boolean isCaptioningEnabled() {
-            if (mExtraBinder != null) {
-                try {
-                    return mExtraBinder.isCaptioningEnabled();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public int getRepeatMode() {
-            if (mExtraBinder != null) {
-                try {
-                    return mExtraBinder.getRepeatMode();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in getRepeatMode.", e);
-                }
-            }
-            return PlaybackStateCompat.REPEAT_MODE_INVALID;
-        }
-
-        @Override
-        public int getShuffleMode() {
-            if (mExtraBinder != null) {
-                try {
-                    return mExtraBinder.getShuffleMode();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Dead object in getShuffleMode.", e);
-                }
-            }
-            return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
-        }
-
-        @Override
-        public long getFlags() {
-            return MediaControllerCompatApi21.getFlags(mControllerObj);
-        }
-
-        @Override
-        public PlaybackInfo getPlaybackInfo() {
-            Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
-            return volumeInfoObj != null ? new PlaybackInfo(
-                    MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
-                    MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
-                    MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
-                    MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
-                    MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
-        }
-
-        @Override
-        public PendingIntent getSessionActivity() {
-            return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
-        }
-
-        @Override
-        public void setVolumeTo(int value, int flags) {
-            MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
-        }
-
-        @Override
-        public void adjustVolume(int direction, int flags) {
-            MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
-        }
-
-        @Override
-        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
-            MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
-        }
-
-        @Override
-        public boolean isSessionReady() {
-            return mExtraBinder != null;
-        }
-
-        @Override
-        public String getPackageName() {
-            return MediaControllerCompatApi21.getPackageName(mControllerObj);
-        }
-
-        @Override
-        public Object getMediaController() {
-            return mControllerObj;
-        }
-
-        private void requestExtraBinder() {
-            sendCommand(COMMAND_GET_EXTRA_BINDER, null,
-                    new ExtraBinderRequestResultReceiver(this, new Handler()));
-        }
-
-        private void processPendingCallbacks() {
-            if (mExtraBinder == null) {
-                return;
-            }
-            synchronized (mPendingCallbacks) {
-                for (Callback callback : mPendingCallbacks) {
-                    ExtraCallback extraCallback = new ExtraCallback(callback);
-                    mCallbackMap.put(callback, extraCallback);
-                    callback.mHasExtraCallback = true;
-                    try {
-                        mExtraBinder.registerCallbackListener(extraCallback);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Dead object in registerCallback.", e);
-                        break;
-                    }
-                    callback.onSessionReady();
-                }
-                mPendingCallbacks.clear();
-            }
-        }
-
-        private static class ExtraBinderRequestResultReceiver extends ResultReceiver {
-            private WeakReference<MediaControllerImplApi21> mMediaControllerImpl;
-
-            public ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl,
-                    Handler handler) {
-                super(handler);
-                mMediaControllerImpl = new WeakReference<>(mediaControllerImpl);
-            }
-
-            @Override
-            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                MediaControllerImplApi21 mediaControllerImpl = mMediaControllerImpl.get();
-                if (mediaControllerImpl == null || resultData == null) {
-                    return;
-                }
-                mediaControllerImpl.mExtraBinder = IMediaSession.Stub.asInterface(
-                        BundleCompat.getBinder(resultData, MediaSessionCompat.EXTRA_BINDER));
-                mediaControllerImpl.processPendingCallbacks();
-            }
-        }
-
-        private static class ExtraCallback extends Callback.StubCompat {
-            ExtraCallback(Callback callback) {
-                super(callback);
-            }
-
-            @Override
-            public void onSessionDestroyed() throws RemoteException {
-                // Will not be called.
-                throw new AssertionError();
-            }
-
-            @Override
-            public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
-                // Will not be called.
-                throw new AssertionError();
-            }
-
-            @Override
-            public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
-                // Will not be called.
-                throw new AssertionError();
-            }
-
-            @Override
-            public void onQueueTitleChanged(CharSequence title) throws RemoteException {
-                // Will not be called.
-                throw new AssertionError();
-            }
-
-            @Override
-            public void onExtrasChanged(Bundle extras) throws RemoteException {
-                // Will not be called.
-                throw new AssertionError();
-            }
-
-            @Override
-            public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
-                // Will not be called.
-                throw new AssertionError();
-            }
-        }
-    }
-
-    static class TransportControlsApi21 extends TransportControls {
-        protected final Object mControlsObj;
-
-        public TransportControlsApi21(Object controlsObj) {
-            mControlsObj = controlsObj;
-        }
-
-        @Override
-        public void prepare() {
-            sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
-        }
-
-        @Override
-        public void prepareFromMediaId(String mediaId, Bundle extras) {
-            Bundle bundle = new Bundle();
-            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
-            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
-            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
-        }
-
-        @Override
-        public void prepareFromSearch(String query, Bundle extras) {
-            Bundle bundle = new Bundle();
-            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
-            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
-            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
-        }
-
-        @Override
-        public void prepareFromUri(Uri uri, Bundle extras) {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
-            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
-            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
-        }
-
-        @Override
-        public void play() {
-            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
-        }
-
-        @Override
-        public void pause() {
-            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
-        }
-
-        @Override
-        public void stop() {
-            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
-        }
-
-        @Override
-        public void seekTo(long pos) {
-            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
-        }
-
-        @Override
-        public void fastForward() {
-            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
-        }
-
-        @Override
-        public void rewind() {
-            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
-        }
-
-        @Override
-        public void skipToNext() {
-            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
-        }
-
-        @Override
-        public void skipToPrevious() {
-            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
-        }
-
-        @Override
-        public void setRating(RatingCompat rating) {
-            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
-                    rating != null ? rating.getRating() : null);
-        }
-
-        @Override
-        public void setRating(RatingCompat rating, Bundle extras) {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_RATING, rating);
-            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
-            sendCustomAction(MediaSessionCompat.ACTION_SET_RATING, bundle);
-        }
-
-        @Override
-        public void setCaptioningEnabled(boolean enabled) {
-            Bundle bundle = new Bundle();
-            bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_CAPTIONING_ENABLED, enabled);
-            sendCustomAction(MediaSessionCompat.ACTION_SET_CAPTIONING_ENABLED, bundle);
-        }
-
-        @Override
-        public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
-            Bundle bundle = new Bundle();
-            bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_REPEAT_MODE, repeatMode);
-            sendCustomAction(MediaSessionCompat.ACTION_SET_REPEAT_MODE, bundle);
-        }
-
-        @Override
-        public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
-            Bundle bundle = new Bundle();
-            bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE, shuffleMode);
-            sendCustomAction(MediaSessionCompat.ACTION_SET_SHUFFLE_MODE, bundle);
-        }
-
-        @Override
-        public void playFromMediaId(String mediaId, Bundle extras) {
-            MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
-                    extras);
-        }
-
-        @Override
-        public void playFromSearch(String query, Bundle extras) {
-            MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
-                    extras);
-        }
-
-        @Override
-        public void playFromUri(Uri uri, Bundle extras) {
-            if (uri == null || Uri.EMPTY.equals(uri)) {
-                throw new IllegalArgumentException(
-                        "You must specify a non-empty Uri for playFromUri.");
-            }
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
-            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
-            sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle);
-        }
-
-        @Override
-        public void skipToQueueItem(long id) {
-            MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
-        }
-
-        @Override
-        public void sendCustomAction(CustomAction customAction, Bundle args) {
-            validateCustomAction(customAction.getAction(), args);
-            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
-                    customAction.getAction(), args);
-        }
-
-        @Override
-        public void sendCustomAction(String action, Bundle args) {
-            validateCustomAction(action, args);
-            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
-                    args);
-        }
-    }
-
-    @RequiresApi(23)
-    static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
-
-        public MediaControllerImplApi23(Context context, MediaSessionCompat session) {
-            super(context, session);
-        }
-
-        public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
-                throws RemoteException {
-            super(context, sessionToken);
-        }
-
-        @Override
-        public TransportControls getTransportControls() {
-            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
-            return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
-        }
-    }
-
-    @RequiresApi(23)
-    static class TransportControlsApi23 extends TransportControlsApi21 {
-
-        public TransportControlsApi23(Object controlsObj) {
-            super(controlsObj);
-        }
-
-        @Override
-        public void playFromUri(Uri uri, Bundle extras) {
-            MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
-                    extras);
-        }
-    }
-
-    @RequiresApi(24)
-    static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
-
-        public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
-            super(context, session);
-        }
-
-        public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
-                throws RemoteException {
-            super(context, sessionToken);
-        }
-
-        @Override
-        public TransportControls getTransportControls() {
-            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
-            return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
-        }
-    }
-
-    @RequiresApi(24)
-    static class TransportControlsApi24 extends TransportControlsApi23 {
-
-        public TransportControlsApi24(Object controlsObj) {
-            super(controlsObj);
-        }
-
-        @Override
-        public void prepare() {
-            MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
-        }
-
-        @Override
-        public void prepareFromMediaId(String mediaId, Bundle extras) {
-            MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
-                    mControlsObj, mediaId, extras);
-        }
-
-        @Override
-        public void prepareFromSearch(String query, Bundle extras) {
-            MediaControllerCompatApi24.TransportControls.prepareFromSearch(
-                    mControlsObj, query, extras);
-        }
-
-        @Override
-        public void prepareFromUri(Uri uri, Bundle extras) {
-            MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
-        }
-    }
-}
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
index 2ad4d86..3227482 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -63,12 +63,11 @@
 import android.os.ResultReceiver;
 import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaControllerCompat.TransportControls;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 
-import androidx.media.session.MediaControllerCompat;
-import androidx.media.session.MediaControllerCompat.TransportControls;
-
 public class ClientBroadcastReceiver extends BroadcastReceiver {
 
     @Override
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
index 3d181eb..227afad 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -71,14 +71,14 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.RatingCompat;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.media.MediaBrowserCompat;
-import androidx.media.MediaBrowserCompat.MediaItem;
 import androidx.media.MediaBrowserServiceCompat;
 
 import org.junit.After;
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
index b25812e..bc03f1a 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
@@ -75,18 +75,18 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
 import android.support.v4.media.session.ParcelableVolumeInfo;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
-import androidx.media.MediaBrowserCompat;
 import androidx.media.VolumeProviderCompat;
-import androidx.media.session.MediaControllerCompat;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaItemTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaItemTest.java
index 52b93c1..179a178 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaItemTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaItemTest.java
@@ -21,10 +21,9 @@
 
 import android.os.Parcel;
 import android.support.test.filters.SmallTest;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.MediaDescriptionCompat;
 
-import androidx.media.MediaBrowserCompat.MediaItem;
-
 import org.junit.Test;
 
 /**
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
index 2120015..3519f2f 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
@@ -90,13 +90,13 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 import android.view.KeyEvent;
 
 import androidx.media.VolumeProviderCompat;
-import androidx.media.session.MediaControllerCompat;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
index f5045ec..d70ce8b 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
@@ -33,12 +33,12 @@
 import static org.junit.Assert.assertNull;
 
 import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 
 import androidx.annotation.NonNull;
-import androidx.media.MediaBrowserCompat.MediaItem;
 import androidx.media.MediaBrowserServiceCompat;
 
 import java.util.ArrayList;
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
index 3e6f09d..7b31196 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
@@ -17,11 +17,11 @@
 package android.support.mediacompat.service;
 
 import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.media.MediaBrowserCompat;
 import androidx.media.MediaBrowserServiceCompat;
 
 import java.util.List;
diff --git a/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java b/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
index 46a8f28..b7d9eb2 100644
--- a/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
+++ b/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
@@ -26,7 +26,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.media.session.MediaSessionCompat;
 
-import androidx.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteControllerDialog.java b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteControllerDialog.java
index 717dbe9..c9fe73a 100644
--- a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteControllerDialog.java
+++ b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteControllerDialog.java
@@ -69,7 +69,7 @@
 import androidx.appcompat.app.AlertDialog;
 import androidx.core.util.ObjectsCompat;
 import androidx.core.view.accessibility.AccessibilityEventCompat;
-import androidx.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import androidx.mediarouter.R;
 import androidx.mediarouter.media.MediaRouteSelector;
 import androidx.mediarouter.media.MediaRouter;
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index bc1d3a8..af21da4 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -26,7 +26,7 @@
 
 dependencies {
     compile(SUPPORT_ANNOTATIONS)
-    compile(project(":arch:common"))
+    compile(project(":arch:core-common"))
 
     testCompile(JUNIT)
     testCompile(MOCKITO_CORE)
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java b/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
deleted file mode 100644
index b2e389f..0000000
--- a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.concurrent.Executor;
-
-abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
-    @Override
-    boolean isContiguous() {
-        return true;
-    }
-
-    abstract void dispatchLoadInitial(
-            @Nullable Key key,
-            int initialLoadSize,
-            int pageSize,
-            boolean enablePlaceholders,
-            @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver);
-
-    abstract void dispatchLoadAfter(
-            int currentEndIndex,
-            @NonNull Value currentEndItem,
-            int pageSize,
-            @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver);
-
-    abstract void dispatchLoadBefore(
-            int currentBeginIndex,
-            @NonNull Value currentBeginItem,
-            int pageSize,
-            @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver);
-
-    /**
-     * Get the key from either the position, or item, or null if position/item invalid.
-     * <p>
-     * Position may not match passed item's position - if trying to query the key from a position
-     * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
-     */
-    abstract Key getKey(int position, Value item);
-}
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java b/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
deleted file mode 100644
index b540ce1..0000000
--- a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.AnyThread;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
-    private final ContiguousDataSource<K, V> mDataSource;
-    private boolean mPrependWorkerRunning = false;
-    private boolean mAppendWorkerRunning = false;
-
-    private int mPrependItemsRequested = 0;
-    private int mAppendItemsRequested = 0;
-
-    private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
-        // Creation thread for initial synchronous load, otherwise main thread
-        // Safe to access main thread only state - no other thread has reference during construction
-        @AnyThread
-        @Override
-        public void onPageResult(@PageResult.ResultType int resultType,
-                @NonNull PageResult<V> pageResult) {
-            if (pageResult.isInvalid()) {
-                detach();
-                return;
-            }
-
-            if (isDetached()) {
-                // No op, have detached
-                return;
-            }
-
-            List<V> page = pageResult.page;
-            if (resultType == PageResult.INIT) {
-                mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
-                        pageResult.positionOffset, ContiguousPagedList.this);
-                if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
-                    // Because the ContiguousPagedList wasn't initialized with a last load position,
-                    // initialize it to the middle of the initial load
-                    mLastLoad =
-                            pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
-                }
-            } else if (resultType == PageResult.APPEND) {
-                mStorage.appendPage(page, ContiguousPagedList.this);
-            } else if (resultType == PageResult.PREPEND) {
-                mStorage.prependPage(page, ContiguousPagedList.this);
-            } else {
-                throw new IllegalArgumentException("unexpected resultType " + resultType);
-            }
-
-
-            if (mBoundaryCallback != null) {
-                boolean deferEmpty = mStorage.size() == 0;
-                boolean deferBegin = !deferEmpty
-                        && resultType == PageResult.PREPEND
-                        && pageResult.page.size() == 0;
-                boolean deferEnd = !deferEmpty
-                        && resultType == PageResult.APPEND
-                        && pageResult.page.size() == 0;
-                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
-            }
-        }
-    };
-
-    static final int LAST_LOAD_UNSPECIFIED = -1;
-
-    ContiguousPagedList(
-            @NonNull ContiguousDataSource<K, V> dataSource,
-            @NonNull Executor mainThreadExecutor,
-            @NonNull Executor backgroundThreadExecutor,
-            @Nullable BoundaryCallback<V> boundaryCallback,
-            @NonNull Config config,
-            final @Nullable K key,
-            int lastLoad) {
-        super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
-                boundaryCallback, config);
-        mDataSource = dataSource;
-        mLastLoad = lastLoad;
-
-        if (mDataSource.isInvalid()) {
-            detach();
-        } else {
-            mDataSource.dispatchLoadInitial(key,
-                    mConfig.initialLoadSizeHint,
-                    mConfig.pageSize,
-                    mConfig.enablePlaceholders,
-                    mMainThreadExecutor,
-                    mReceiver);
-        }
-    }
-
-    @MainThread
-    @Override
-    void dispatchUpdatesSinceSnapshot(
-            @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
-        final PagedStorage<V> snapshot = pagedListSnapshot.mStorage;
-
-        final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
-        final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
-
-        final int previousTrailing = snapshot.getTrailingNullCount();
-        final int previousLeading = snapshot.getLeadingNullCount();
-
-        // Validate that the snapshot looks like a previous version of this list - if it's not,
-        // we can't be sure we'll dispatch callbacks safely
-        if (snapshot.isEmpty()
-                || newlyAppended < 0
-                || newlyPrepended < 0
-                || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
-                || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
-                || (mStorage.getStorageCount()
-                        != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
-            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
-                    + " to be a snapshot of this PagedList");
-        }
-
-        if (newlyAppended != 0) {
-            final int changedCount = Math.min(previousTrailing, newlyAppended);
-            final int addedCount = newlyAppended - changedCount;
-
-            final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
-            if (changedCount != 0) {
-                callback.onChanged(endPosition, changedCount);
-            }
-            if (addedCount != 0) {
-                callback.onInserted(endPosition + changedCount, addedCount);
-            }
-        }
-        if (newlyPrepended != 0) {
-            final int changedCount = Math.min(previousLeading, newlyPrepended);
-            final int addedCount = newlyPrepended - changedCount;
-
-            if (changedCount != 0) {
-                callback.onChanged(previousLeading, changedCount);
-            }
-            if (addedCount != 0) {
-                callback.onInserted(0, addedCount);
-            }
-        }
-    }
-
-    @MainThread
-    @Override
-    protected void loadAroundInternal(int index) {
-        int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
-        int appendItems = index + mConfig.prefetchDistance
-                - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
-
-        mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
-        if (mPrependItemsRequested > 0) {
-            schedulePrepend();
-        }
-
-        mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
-        if (mAppendItemsRequested > 0) {
-            scheduleAppend();
-        }
-    }
-
-    @MainThread
-    private void schedulePrepend() {
-        if (mPrependWorkerRunning) {
-            return;
-        }
-        mPrependWorkerRunning = true;
-
-        final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
-
-        // safe to access first item here - mStorage can't be empty if we're prepending
-        final V item = mStorage.getFirstLoadedItem();
-        mBackgroundThreadExecutor.execute(new Runnable() {
-            @Override
-            public void run() {
-                if (isDetached()) {
-                    return;
-                }
-                if (mDataSource.isInvalid()) {
-                    detach();
-                } else {
-                    mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize,
-                            mMainThreadExecutor, mReceiver);
-                }
-
-            }
-        });
-    }
-
-    @MainThread
-    private void scheduleAppend() {
-        if (mAppendWorkerRunning) {
-            return;
-        }
-        mAppendWorkerRunning = true;
-
-        final int position = mStorage.getLeadingNullCount()
-                + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
-
-        // safe to access first item here - mStorage can't be empty if we're appending
-        final V item = mStorage.getLastLoadedItem();
-        mBackgroundThreadExecutor.execute(new Runnable() {
-            @Override
-            public void run() {
-                if (isDetached()) {
-                    return;
-                }
-                if (mDataSource.isInvalid()) {
-                    detach();
-                } else {
-                    mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
-                            mMainThreadExecutor, mReceiver);
-                }
-            }
-        });
-    }
-
-    @Override
-    boolean isContiguous() {
-        return true;
-    }
-
-    @NonNull
-    @Override
-    public DataSource<?, V> getDataSource() {
-        return mDataSource;
-    }
-
-    @Nullable
-    @Override
-    public Object getLastKey() {
-        return mDataSource.getKey(mLastLoad, mLastItem);
-    }
-
-    @MainThread
-    @Override
-    public void onInitialized(int count) {
-        notifyInserted(0, count);
-    }
-
-    @MainThread
-    @Override
-    public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
-        // consider whether to post more work, now that a page is fully prepended
-        mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
-        mPrependWorkerRunning = false;
-        if (mPrependItemsRequested > 0) {
-            // not done prepending, keep going
-            schedulePrepend();
-        }
-
-        // finally dispatch callbacks, after prepend may have already been scheduled
-        notifyChanged(leadingNulls, changedCount);
-        notifyInserted(0, addedCount);
-
-        offsetBoundaryAccessIndices(addedCount);
-    }
-
-    @MainThread
-    @Override
-    public void onPageAppended(int endPosition, int changedCount, int addedCount) {
-        // consider whether to post more work, now that a page is fully appended
-
-        mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
-        mAppendWorkerRunning = false;
-        if (mAppendItemsRequested > 0) {
-            // not done appending, keep going
-            scheduleAppend();
-        }
-
-        // finally dispatch callbacks, after append may have already been scheduled
-        notifyChanged(endPosition, changedCount);
-        notifyInserted(endPosition + changedCount, addedCount);
-    }
-
-    @MainThread
-    @Override
-    public void onPagePlaceholderInserted(int pageIndex) {
-        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
-    }
-
-    @MainThread
-    @Override
-    public void onPageInserted(int start, int count) {
-        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/DataSource.java b/paging/common/src/main/java/android/arch/paging/DataSource.java
deleted file mode 100644
index 91360b0..0000000
--- a/paging/common/src/main/java/android/arch/paging/DataSource.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.arch.core.util.Function;
-import android.support.annotation.AnyThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Base class for loading pages of snapshot data into a {@link PagedList}.
- * <p>
- * DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as
- * it loads more data, but the data loaded cannot be updated. If the underlying data set is
- * modified, a new PagedList / DataSource pair must be created to represent the new data.
- * <h4>Loading Pages</h4>
- * PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter}
- * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
- * <p>
- * To control how and when a PagedList queries data from its DataSource, see
- * {@link PagedList.Config}. The Config object defines things like load sizes and prefetch distance.
- * <h4>Updating Paged Data</h4>
- * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
- * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
- * content update occurs. A DataSource must detect that it cannot continue loading its
- * snapshot (for instance, when Database query notices a table being invalidated), and call
- * {@link #invalidate()}. Then a new PagedList / DataSource pair would be created to load data from
- * the new state of the Database query.
- * <p>
- * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
- * PagedList. For example, loading from network when the network's paging API doesn't provide
- * updates.
- * <p>
- * To page in data from a source that does provide updates, you can create a
- * {@link DataSource.Factory}, where each DataSource created is invalidated when an update to the
- * data set occurs that makes the current snapshot invalid. For example, when paging a query from
- * the Database, and the table being queried inserts or removes items. You can also use a
- * DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content
- * (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data,
- * you can connect an explicit refresh signal to call {@link #invalidate()} on the current
- * DataSource.
- * <p>
- * If you have more granular update signals, such as a network API signaling an update to a single
- * item in the list, it's recommended to load data from network into memory. Then present that
- * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
- * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
- * snapshot can be created.
- * <h4>Implementing a DataSource</h4>
- * To implement, extend one of the subclasses: {@link PageKeyedDataSource},
- * {@link ItemKeyedDataSource}, or {@link PositionalDataSource}.
- * <p>
- * Use {@link PageKeyedDataSource} if pages you load embed keys for loading adjacent pages. For
- * example a network response that returns some items, and a next/previous page links.
- * <p>
- * Use {@link ItemKeyedDataSource} if you need to use data from item {@code N-1} to load item
- * {@code N}. For example, if requesting the backend for the next comments in the list
- * requires the ID or timestamp of the most recent loaded comment, or if querying the next users
- * from a name-sorted database query requires the name and unique ID of the previous.
- * <p>
- * Use {@link PositionalDataSource} if you can load pages of a requested size at arbitrary
- * positions, and provide a fixed item count. PositionalDataSource supports querying pages at
- * arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that
- * PositionalDataSource is required to respect page size for efficient tiling. If you want to
- * override page size (e.g. when network page size constraints are only known at runtime), use one
- * of the other DataSource classes.
- * <p>
- * Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not
- * return {@code null} items in lists that it loads. This is so that users of the PagedList
- * can differentiate unloaded placeholder items from content that has been paged in.
- *
- * @param <Key> Input used to trigger initial load from the DataSource. Often an Integer position.
- * @param <Value> Value type loaded by the DataSource.
- */
-@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
-public abstract class DataSource<Key, Value> {
-    /**
-     * Factory for DataSources.
-     * <p>
-     * Data-loading systems of an application or library can implement this interface to allow
-     * {@code LiveData<PagedList>}s to be created. For example, Room can provide a
-     * DataSource.Factory for a given SQL query:
-     *
-     * <pre>
-     * {@literal @}Dao
-     * interface UserDao {
-     *    {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
-     *    public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
-     * }
-     * </pre>
-     * In the above sample, {@code Integer} is used because it is the {@code Key} type of
-     * PositionalDataSource. Currently, Room uses the {@code LIMIT}/{@code OFFSET} SQL keywords to
-     * page a large query with a PositionalDataSource.
-     *
-     * @param <Key> Key identifying items in DataSource.
-     * @param <Value> Type of items in the list loaded by the DataSources.
-     */
-    public abstract static class Factory<Key, Value> {
-        /**
-         * Create a DataSource.
-         * <p>
-         * The DataSource should invalidate itself if the snapshot is no longer valid. If a
-         * DataSource becomes invalid, the only way to query more data is to create a new DataSource
-         * from the Factory.
-         * <p>
-         * {@link LivePagedListBuilder} for example will construct a new PagedList and DataSource
-         * when the current DataSource is invalidated, and pass the new PagedList through the
-         * {@code LiveData<PagedList>} to observers.
-         *
-         * @return the new DataSource.
-         */
-        public abstract DataSource<Key, Value> create();
-
-        /**
-         * Applies the given function to each value emitted by DataSources produced by this Factory.
-         * <p>
-         * Same as {@link #mapByPage(Function)}, but operates on individual items.
-         *
-         * @param function Function that runs on each loaded item, returning items of a potentially
-         *                  new type.
-         * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
-         *
-         * @return A new DataSource.Factory, which transforms items using the given function.
-         *
-         * @see #mapByPage(Function)
-         * @see DataSource#map(Function)
-         * @see DataSource#mapByPage(Function)
-         */
-        @NonNull
-        public <ToValue> DataSource.Factory<Key, ToValue> map(
-                @NonNull Function<Value, ToValue> function) {
-            return mapByPage(createListFunction(function));
-        }
-
-        /**
-         * Applies the given function to each value emitted by DataSources produced by this Factory.
-         * <p>
-         * Same as {@link #map(Function)}, but allows for batch conversions.
-         *
-         * @param function Function that runs on each loaded page, returning items of a potentially
-         *                  new type.
-         * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
-         *
-         * @return A new DataSource.Factory, which transforms items using the given function.
-         *
-         * @see #map(Function)
-         * @see DataSource#map(Function)
-         * @see DataSource#mapByPage(Function)
-         */
-        @NonNull
-        public <ToValue> DataSource.Factory<Key, ToValue> mapByPage(
-                @NonNull final Function<List<Value>, List<ToValue>> function) {
-            return new Factory<Key, ToValue>() {
-                @Override
-                public DataSource<Key, ToValue> create() {
-                    return Factory.this.create().mapByPage(function);
-                }
-            };
-        }
-    }
-
-    @NonNull
-    static <X, Y> Function<List<X>, List<Y>> createListFunction(
-            final @NonNull Function<X, Y> innerFunc) {
-        return new Function<List<X>, List<Y>>() {
-            @Override
-            public List<Y> apply(@NonNull List<X> source) {
-                List<Y> out = new ArrayList<>(source.size());
-                for (int i = 0; i < source.size(); i++) {
-                    out.add(innerFunc.apply(source.get(i)));
-                }
-                return out;
-            }
-        };
-    }
-
-    static <A, B> List<B> convert(Function<List<A>, List<B>> function, List<A> source) {
-        List<B> dest = function.apply(source);
-        if (dest.size() != source.size()) {
-            throw new IllegalStateException("Invalid Function " + function
-                    + " changed return size. This is not supported.");
-        }
-        return dest;
-    }
-
-    // Since we currently rely on implementation details of two implementations,
-    // prevent external subclassing, except through exposed subclasses
-    DataSource() {
-    }
-
-    /**
-     * Applies the given function to each value emitted by the DataSource.
-     * <p>
-     * Same as {@link #map(Function)}, but allows for batch conversions.
-     *
-     * @param function Function that runs on each loaded page, returning items of a potentially
-     *                  new type.
-     * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
-     *
-     * @return A new DataSource, which transforms items using the given function.
-     *
-     * @see #map(Function)
-     * @see DataSource.Factory#map(Function)
-     * @see DataSource.Factory#mapByPage(Function)
-     */
-    @NonNull
-    public abstract <ToValue> DataSource<Key, ToValue> mapByPage(
-            @NonNull Function<List<Value>, List<ToValue>> function);
-
-    /**
-     * Applies the given function to each value emitted by the DataSource.
-     * <p>
-     * Same as {@link #mapByPage(Function)}, but operates on individual items.
-     *
-     * @param function Function that runs on each loaded item, returning items of a potentially
-     *                  new type.
-     * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
-     *
-     * @return A new DataSource, which transforms items using the given function.
-     *
-     * @see #mapByPage(Function)
-     * @see DataSource.Factory#map(Function)
-     * @see DataSource.Factory#mapByPage(Function)
-     */
-    @NonNull
-    public abstract <ToValue> DataSource<Key, ToValue> map(
-            @NonNull Function<Value, ToValue> function);
-
-    /**
-     * Returns true if the data source guaranteed to produce a contiguous set of items,
-     * never producing gaps.
-     */
-    abstract boolean isContiguous();
-
-    static class LoadCallbackHelper<T> {
-        static void validateInitialLoadParams(@NonNull List<?> data, int position, int totalCount) {
-            if (position < 0) {
-                throw new IllegalArgumentException("Position must be non-negative");
-            }
-            if (data.size() + position > totalCount) {
-                throw new IllegalArgumentException(
-                        "List size + position too large, last item in list beyond totalCount.");
-            }
-            if (data.size() == 0 && totalCount > 0) {
-                throw new IllegalArgumentException(
-                        "Initial result cannot be empty if items are present in data set.");
-            }
-        }
-
-        @PageResult.ResultType
-        final int mResultType;
-        private final DataSource mDataSource;
-        private final PageResult.Receiver<T> mReceiver;
-
-        // mSignalLock protects mPostExecutor, and mHasSignalled
-        private final Object mSignalLock = new Object();
-        private Executor mPostExecutor = null;
-        private boolean mHasSignalled = false;
-
-        LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType,
-                @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
-            mDataSource = dataSource;
-            mResultType = resultType;
-            mPostExecutor = mainThreadExecutor;
-            mReceiver = receiver;
-        }
-
-        void setPostExecutor(Executor postExecutor) {
-            synchronized (mSignalLock) {
-                mPostExecutor = postExecutor;
-            }
-        }
-
-        /**
-         * Call before verifying args, or dispatching actul results
-         *
-         * @return true if DataSource was invalid, and invalid result dispatched
-         */
-        boolean dispatchInvalidResultIfInvalid() {
-            if (mDataSource.isInvalid()) {
-                dispatchResultToReceiver(PageResult.<T>getInvalidResult());
-                return true;
-            }
-            return false;
-        }
-
-        void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
-            Executor executor;
-            synchronized (mSignalLock) {
-                if (mHasSignalled) {
-                    throw new IllegalStateException(
-                            "callback.onResult already called, cannot call again.");
-                }
-                mHasSignalled = true;
-                executor = mPostExecutor;
-            }
-
-            if (executor != null) {
-                executor.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        mReceiver.onPageResult(mResultType, result);
-                    }
-                });
-            } else {
-                mReceiver.onPageResult(mResultType, result);
-            }
-        }
-    }
-
-    /**
-     * Invalidation callback for DataSource.
-     * <p>
-     * Used to signal when a DataSource a data source has become invalid, and that a new data source
-     * is needed to continue loading data.
-     */
-    public interface InvalidatedCallback {
-        /**
-         * Called when the data backing the list has become invalid. This callback is typically used
-         * to signal that a new data source is needed.
-         * <p>
-         * This callback will be invoked on the thread that calls {@link #invalidate()}. It is valid
-         * for the data source to invalidate itself during its load methods, or for an outside
-         * source to invalidate it.
-         */
-        @AnyThread
-        void onInvalidated();
-    }
-
-    private AtomicBoolean mInvalid = new AtomicBoolean(false);
-
-    private CopyOnWriteArrayList<InvalidatedCallback> mOnInvalidatedCallbacks =
-            new CopyOnWriteArrayList<>();
-
-    /**
-     * Add a callback to invoke when the DataSource is first invalidated.
-     * <p>
-     * Once invalidated, a data source will not become valid again.
-     * <p>
-     * A data source will only invoke its callbacks once - the first time {@link #invalidate()}
-     * is called, on that thread.
-     *
-     * @param onInvalidatedCallback The callback, will be invoked on thread that
-     *                              {@link #invalidate()} is called on.
-     */
-    @AnyThread
-    @SuppressWarnings("WeakerAccess")
-    public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
-        mOnInvalidatedCallbacks.add(onInvalidatedCallback);
-    }
-
-    /**
-     * Remove a previously added invalidate callback.
-     *
-     * @param onInvalidatedCallback The previously added callback.
-     */
-    @AnyThread
-    @SuppressWarnings("WeakerAccess")
-    public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
-        mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
-    }
-
-    /**
-     * Signal the data source to stop loading, and notify its callback.
-     * <p>
-     * If invalidate has already been called, this method does nothing.
-     */
-    @AnyThread
-    public void invalidate() {
-        if (mInvalid.compareAndSet(false, true)) {
-            for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
-                callback.onInvalidated();
-            }
-        }
-    }
-
-    /**
-     * Returns true if the data source is invalid, and can no longer be queried for data.
-     *
-     * @return True if the data source is invalid, and can no longer return data.
-     */
-    @WorkerThread
-    public boolean isInvalid() {
-        return mInvalid.get();
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/ItemKeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/ItemKeyedDataSource.java
deleted file mode 100644
index f68fae1..0000000
--- a/paging/common/src/main/java/android/arch/paging/ItemKeyedDataSource.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright 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.paging;
-
-import android.arch.core.util.Function;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Incremental data loader for paging keyed content, where loaded content uses previously loaded
- * items as input to future loads.
- * <p>
- * Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
- * to load item {@code N}. This is common, for example, in sorted database queries where
- * attributes of the item such just before the next query define how to execute it.
- * <p>
- * The {@code InMemoryByItemRepository} in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network ItemKeyedDataSource using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
-
-    /**
-     * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
-     *
-     * @param <Key> Type of data used to query Value types out of the DataSource.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static class LoadInitialParams<Key> {
-        /**
-         * Load items around this key, or at the beginning of the data set if {@code null} is
-         * passed.
-         * <p>
-         * Note that this key is generally a hint, and may be ignored if you want to always load
-         * from the beginning.
-         */
-        @Nullable
-        public final Key requestedInitialKey;
-
-        /**
-         * Requested number of items to load.
-         * <p>
-         * Note that this may be larger than available data.
-         */
-        public final int requestedLoadSize;
-
-        /**
-         * Defines whether placeholders are enabled, and whether the total count passed to
-         * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored.
-         */
-        public final boolean placeholdersEnabled;
-
-
-        public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
-                boolean placeholdersEnabled) {
-            this.requestedInitialKey = requestedInitialKey;
-            this.requestedLoadSize = requestedLoadSize;
-            this.placeholdersEnabled = placeholdersEnabled;
-        }
-    }
-
-    /**
-     * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)}
-     * and {@link #loadAfter(LoadParams, LoadCallback)}.
-     *
-     * @param <Key> Type of data used to query Value types out of the DataSource.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static class LoadParams<Key> {
-        /**
-         * Load items before/after this key.
-         * <p>
-         * Returned data must begin directly adjacent to this position.
-         */
-        public final Key key;
-        /**
-         * Requested number of items to load.
-         * <p>
-         * Returned page can be of this size, but it may be altered if that is easier, e.g. a
-         * network data source where the backend defines page size.
-         */
-        public final int requestedLoadSize;
-
-        public LoadParams(Key key, int requestedLoadSize) {
-            this.key = key;
-            this.requestedLoadSize = requestedLoadSize;
-        }
-    }
-
-    /**
-     * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
-     * to return data and, optionally, position/count information.
-     * <p>
-     * A callback can be called only once, and will throw if called again.
-     * <p>
-     * If you can compute the number of items in the data set before and after the loaded range,
-     * call the three parameter {@link #onResult(List, int, int)} to pass that information. You
-     * can skip passing this information by calling the single parameter {@link #onResult(List)},
-     * either if it's difficult to compute, or if {@link LoadInitialParams#placeholdersEnabled} is
-     * {@code false}, so the positioning information will be ignored.
-     * <p>
-     * It is always valid for a DataSource loading method that takes a callback to stash the
-     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
-     * temporary, recoverable error states (such as a network error that can be retried).
-     *
-     * @param <Value> Type of items being loaded.
-     */
-    public abstract static class LoadInitialCallback<Value> extends LoadCallback<Value> {
-        /**
-         * Called to pass initial load state from a DataSource.
-         * <p>
-         * Call this method from your DataSource's {@code loadInitial} function to return data,
-         * and inform how many placeholders should be shown before and after. If counting is cheap
-         * to compute (for example, if a network load returns the information regardless), it's
-         * recommended to pass data back through this method.
-         * <p>
-         * It is always valid to pass a different amount of data than what is requested. Pass an
-         * empty list if there is no more data to load.
-         *
-         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
-         *             is treated as empty, and no further loads will occur.
-         * @param position Position of the item at the front of the list. If there are {@code N}
-         *                 items before the items in data that can be loaded from this DataSource,
-         *                 pass {@code N}.
-         * @param totalCount Total number of items that may be returned from this DataSource.
-         *                   Includes the number in the initial {@code data} parameter
-         *                   as well as any items that can be loaded in front or behind of
-         *                   {@code data}.
-         */
-        public abstract void onResult(@NonNull List<Value> data, int position, int totalCount);
-    }
-
-
-    /**
-     * Callback for ItemKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)}
-     * and {@link #loadAfter(LoadParams, LoadCallback)} to return data.
-     * <p>
-     * A callback can be called only once, and will throw if called again.
-     * <p>
-     * It is always valid for a DataSource loading method that takes a callback to stash the
-     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
-     * temporary, recoverable error states (such as a network error that can be retried).
-     *
-     * @param <Value> Type of items being loaded.
-     */
-    public abstract static class LoadCallback<Value> {
-        /**
-         * Called to pass loaded data from a DataSource.
-         * <p>
-         * Call this method from your ItemKeyedDataSource's
-         * {@link #loadBefore(LoadParams, LoadCallback)} and
-         * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
-         * <p>
-         * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
-         * initialize without counting available data, or supporting placeholders.
-         * <p>
-         * It is always valid to pass a different amount of data than what is requested. Pass an
-         * empty list if there is no more data to load.
-         *
-         * @param data List of items loaded from the ItemKeyedDataSource.
-         */
-        public abstract void onResult(@NonNull List<Value> data);
-    }
-
-    static class LoadInitialCallbackImpl<Value> extends LoadInitialCallback<Value> {
-        final LoadCallbackHelper<Value> mCallbackHelper;
-        private final boolean mCountingEnabled;
-        LoadInitialCallbackImpl(@NonNull ItemKeyedDataSource dataSource, boolean countingEnabled,
-                @NonNull PageResult.Receiver<Value> receiver) {
-            mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver);
-            mCountingEnabled = countingEnabled;
-        }
-
-        @Override
-        public void onResult(@NonNull List<Value> data, int position, int totalCount) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
-
-                int trailingUnloadedCount = totalCount - position - data.size();
-                if (mCountingEnabled) {
-                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
-                            data, position, trailingUnloadedCount, 0));
-                } else {
-                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
-                }
-            }
-        }
-
-        @Override
-        public void onResult(@NonNull List<Value> data) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
-            }
-        }
-    }
-
-    static class LoadCallbackImpl<Value> extends LoadCallback<Value> {
-        final LoadCallbackHelper<Value> mCallbackHelper;
-
-        LoadCallbackImpl(@NonNull ItemKeyedDataSource dataSource, @PageResult.ResultType int type,
-                @Nullable Executor mainThreadExecutor,
-                @NonNull PageResult.Receiver<Value> receiver) {
-            mCallbackHelper = new LoadCallbackHelper<>(
-                    dataSource, type, mainThreadExecutor, receiver);
-        }
-
-        @Override
-        public void onResult(@NonNull List<Value> data) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
-            }
-        }
-    }
-
-    @Nullable
-    @Override
-    final Key getKey(int position, Value item) {
-        if (item == null) {
-            return null;
-        }
-
-        return getKey(item);
-    }
-
-    @Override
-    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
-            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver) {
-        LoadInitialCallbackImpl<Value> callback =
-                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
-        loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
-
-        // If initialLoad's callback is not called within the body, we force any following calls
-        // to post to the UI thread. This constructor may be run on a background thread, but
-        // after constructor, mutation must happen on UI thread.
-        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
-    }
-
-    @Override
-    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
-            int pageSize, @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver) {
-        loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize),
-                new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
-    }
-
-    @Override
-    final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
-            int pageSize, @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver) {
-        loadBefore(new LoadParams<>(getKey(currentBeginItem), pageSize),
-                new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
-    }
-
-    /**
-     * Load initial data.
-     * <p>
-     * This method is called first to initialize a PagedList with data. If it's possible to count
-     * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
-     * the callback via the three-parameter
-     * {@link LoadInitialCallback#onResult(List, int, int)}. This enables PagedLists
-     * presenting data from this source to display placeholders to represent unloaded items.
-     * <p>
-     * {@link LoadInitialParams#requestedInitialKey} and {@link LoadInitialParams#requestedLoadSize}
-     * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
-     * {@code requestedInitialKey} can prevent subsequent PagedList/DataSource pairs from
-     * initializing at the same location. If your data source never invalidates (for example,
-     * loading from the network without the network ever signalling that old data must be reloaded),
-     * it's fine to ignore the {@code initialLoadKey} and always start from the beginning of the
-     * data set.
-     *
-     * @param params Parameters for initial load, including initial key and requested size.
-     * @param callback Callback that receives initial load data.
-     */
-    public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
-            @NonNull LoadInitialCallback<Value> callback);
-
-    /**
-     * Load list data after the key specified in {@link LoadParams#key LoadParams.key}.
-     * <p>
-     * It's valid to return a different list size than the page size if it's easier, e.g. if your
-     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
-     * <p>
-     * Data may be passed synchronously during the loadAfter method, or deferred and called at a
-     * later time. Further loads going down will be blocked until the callback is called.
-     * <p>
-     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
-     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
-     * and prevent further loading.
-     *
-     * @param params Parameters for the load, including the key to load after, and requested size.
-     * @param callback Callback that receives loaded data.
-     */
-    public abstract void loadAfter(@NonNull LoadParams<Key> params,
-            @NonNull LoadCallback<Value> callback);
-
-    /**
-     * Load list data before the key specified in {@link LoadParams#key LoadParams.key}.
-     * <p>
-     * It's valid to return a different list size than the page size if it's easier, e.g. if your
-     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
-     * <p>
-     * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
-     * passed, so if you vary size, ensure that the last item is adjacent to the passed key.
-     * <p>
-     * Data may be passed synchronously during the loadBefore method, or deferred and called at a
-     * later time. Further loads going up will be blocked until the callback is called.
-     * <p>
-     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
-     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
-     * and prevent further loading.
-     *
-     * @param params Parameters for the load, including the key to load before, and requested size.
-     * @param callback Callback that receives loaded data.
-     */
-    public abstract void loadBefore(@NonNull LoadParams<Key> params,
-            @NonNull LoadCallback<Value> callback);
-
-    /**
-     * Return a key associated with the given item.
-     * <p>
-     * If your ItemKeyedDataSource 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(LoadParams, LoadCallback)} or
-     * {@link #loadAfter(LoadParams, LoadCallback)} 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
-    public abstract Key getKey(@NonNull Value item);
-
-    @NonNull
-    @Override
-    public final <ToValue> ItemKeyedDataSource<Key, ToValue> mapByPage(
-            @NonNull Function<List<Value>, List<ToValue>> function) {
-        return new WrapperItemKeyedDataSource<>(this, function);
-    }
-
-    @NonNull
-    @Override
-    public final <ToValue> ItemKeyedDataSource<Key, ToValue> map(
-            @NonNull Function<Value, ToValue> function) {
-        return mapByPage(createListFunction(function));
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/ListDataSource.java b/paging/common/src/main/java/android/arch/paging/ListDataSource.java
deleted file mode 100644
index 1482a91..0000000
--- a/paging/common/src/main/java/android/arch/paging/ListDataSource.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class ListDataSource<T> extends PositionalDataSource<T> {
-    private final List<T> mList;
-
-    public ListDataSource(List<T> list) {
-        mList = new ArrayList<>(list);
-    }
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams params,
-            @NonNull LoadInitialCallback<T> callback) {
-        final int totalCount = mList.size();
-
-        final int position = computeInitialLoadPosition(params, totalCount);
-        final int loadSize = computeInitialLoadSize(params, position, totalCount);
-
-        // for simplicity, we could return everything immediately,
-        // but we tile here since it's expected behavior
-        List<T> sublist = mList.subList(position, position + loadSize);
-        callback.onResult(sublist, position, totalCount);
-    }
-
-    @Override
-    public void loadRange(@NonNull LoadRangeParams params,
-            @NonNull LoadRangeCallback<T> callback) {
-        callback.onResult(mList.subList(params.startPosition,
-                params.startPosition + params.loadSize));
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/PageKeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/PageKeyedDataSource.java
deleted file mode 100644
index 7fc5166..0000000
--- a/paging/common/src/main/java/android/arch/paging/PageKeyedDataSource.java
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright 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.paging;
-
-import android.arch.core.util.Function;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Incremental data loader for page-keyed content, where requests return keys for next/previous
- * pages.
- * <p>
- * Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
- * to load page {@code N}. This is common, for example, in network APIs that include a next/previous
- * link or key with each page load.
- * <p>
- * The {@code InMemoryByPageRepository} in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network PageKeyedDataSource using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class PageKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
-    private final Object mKeyLock = new Object();
-
-    @Nullable
-    @GuardedBy("mKeyLock")
-    private Key mNextKey = null;
-
-    @Nullable
-    @GuardedBy("mKeyLock")
-    private Key mPreviousKey = null;
-
-    private void initKeys(@Nullable Key previousKey, @Nullable Key nextKey) {
-        synchronized (mKeyLock) {
-            mPreviousKey = previousKey;
-            mNextKey = nextKey;
-        }
-    }
-
-    private void setPreviousKey(@Nullable Key previousKey) {
-        synchronized (mKeyLock) {
-            mPreviousKey = previousKey;
-        }
-    }
-
-    private void setNextKey(@Nullable Key nextKey) {
-        synchronized (mKeyLock) {
-            mNextKey = nextKey;
-        }
-    }
-
-    private @Nullable Key getPreviousKey() {
-        synchronized (mKeyLock) {
-            return mPreviousKey;
-        }
-    }
-
-    private @Nullable Key getNextKey() {
-        synchronized (mKeyLock) {
-            return mNextKey;
-        }
-    }
-
-    /**
-     * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
-     *
-     * @param <Key> Type of data used to query pages.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static class LoadInitialParams<Key> {
-        /**
-         * Requested number of items to load.
-         * <p>
-         * Note that this may be larger than available data.
-         */
-        public final int requestedLoadSize;
-
-        /**
-         * Defines whether placeholders are enabled, and whether the total count passed to
-         * {@link LoadInitialCallback#onResult(List, int, int, Key, Key)} will be ignored.
-         */
-        public final boolean placeholdersEnabled;
-
-
-        public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
-            this.requestedLoadSize = requestedLoadSize;
-            this.placeholdersEnabled = placeholdersEnabled;
-        }
-    }
-
-    /**
-     * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)} and
-     * {@link #loadAfter(LoadParams, LoadCallback)}.
-     *
-     * @param <Key> Type of data used to query pages.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static class LoadParams<Key> {
-        /**
-         * Load items before/after this key.
-         * <p>
-         * Returned data must begin directly adjacent to this position.
-         */
-        public final Key key;
-
-        /**
-         * Requested number of items to load.
-         * <p>
-         * Returned page can be of this size, but it may be altered if that is easier, e.g. a
-         * network data source where the backend defines page size.
-         */
-        public final int requestedLoadSize;
-
-        public LoadParams(Key key, int requestedLoadSize) {
-            this.key = key;
-            this.requestedLoadSize = requestedLoadSize;
-        }
-    }
-
-    /**
-     * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
-     * to return data and, optionally, position/count information.
-     * <p>
-     * A callback can be called only once, and will throw if called again.
-     * <p>
-     * If you can compute the number of items in the data set before and after the loaded range,
-     * call the five parameter {@link #onResult(List, int, int, Object, Object)} to pass that
-     * information. You can skip passing this information by calling the three parameter
-     * {@link #onResult(List, Object, Object)}, either if it's difficult to compute, or if
-     * {@link LoadInitialParams#placeholdersEnabled} is {@code false}, so the positioning
-     * information will be ignored.
-     * <p>
-     * It is always valid for a DataSource loading method that takes a callback to stash the
-     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
-     * temporary, recoverable error states (such as a network error that can be retried).
-     *
-     * @param <Key> Type of data used to query pages.
-     * @param <Value> Type of items being loaded.
-     */
-    public abstract static class LoadInitialCallback<Key, Value> {
-        /**
-         * Called to pass initial load state from a DataSource.
-         * <p>
-         * Call this method from your DataSource's {@code loadInitial} function to return data,
-         * and inform how many placeholders should be shown before and after. If counting is cheap
-         * to compute (for example, if a network load returns the information regardless), it's
-         * recommended to pass data back through this method.
-         * <p>
-         * It is always valid to pass a different amount of data than what is requested. Pass an
-         * empty list if there is no more data to load.
-         *
-         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
-         *             is treated as empty, and no further loads will occur.
-         * @param position Position of the item at the front of the list. If there are {@code N}
-         *                 items before the items in data that can be loaded from this DataSource,
-         *                 pass {@code N}.
-         * @param totalCount Total number of items that may be returned from this DataSource.
-         *                   Includes the number in the initial {@code data} parameter
-         *                   as well as any items that can be loaded in front or behind of
-         *                   {@code data}.
-         */
-        public abstract void onResult(@NonNull List<Value> data, int position, int totalCount,
-                @Nullable Key previousPageKey, @Nullable Key nextPageKey);
-
-        /**
-         * Called to pass loaded data from a DataSource.
-         * <p>
-         * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
-         * initialize without counting available data, or supporting placeholders.
-         * <p>
-         * It is always valid to pass a different amount of data than what is requested. Pass an
-         * empty list if there is no more data to load.
-         *
-         * @param data List of items loaded from the PageKeyedDataSource.
-         * @param previousPageKey Key for page before the initial load result, or {@code null} if no
-         *                        more data can be loaded before.
-         * @param nextPageKey Key for page after the initial load result, or {@code null} if no
-         *                        more data can be loaded after.
-         */
-        public abstract void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
-                @Nullable Key nextPageKey);
-    }
-
-    /**
-     * Callback for PageKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)} and
-     * {@link #loadAfter(LoadParams, LoadCallback)} to return data.
-     * <p>
-     * A callback can be called only once, and will throw if called again.
-     * <p>
-     * It is always valid for a DataSource loading method that takes a callback to stash the
-     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
-     * temporary, recoverable error states (such as a network error that can be retried).
-     *
-     * @param <Key> Type of data used to query pages.
-     * @param <Value> Type of items being loaded.
-     */
-    public abstract static class LoadCallback<Key, Value> {
-
-        /**
-         * Called to pass loaded data from a DataSource.
-         * <p>
-         * Call this method from your PageKeyedDataSource's
-         * {@link #loadBefore(LoadParams, LoadCallback)} and
-         * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
-         * <p>
-         * It is always valid to pass a different amount of data than what is requested. Pass an
-         * empty list if there is no more data to load.
-         * <p>
-         * Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've
-         * loaded a page in {@link #loadBefore(LoadParams, LoadCallback)}, pass the key for the
-         * previous page, or {@code null} if the loaded page is the first. If in
-         * {@link #loadAfter(LoadParams, LoadCallback)}, pass the key for the next page, or
-         * {@code null} if the loaded page is the last.
-         *
-         * @param data List of items loaded from the PageKeyedDataSource.
-         * @param adjacentPageKey Key for subsequent page load (previous page in {@link #loadBefore}
-         *                        / next page in {@link #loadAfter}), or {@code null} if there are
-         *                        no more pages to load in the current load direction.
-         */
-        public abstract void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey);
-    }
-
-    static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
-        final LoadCallbackHelper<Value> mCallbackHelper;
-        private final PageKeyedDataSource<Key, Value> mDataSource;
-        private final boolean mCountingEnabled;
-        LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
-                boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
-            mCallbackHelper = new LoadCallbackHelper<>(
-                    dataSource, PageResult.INIT, null, receiver);
-            mDataSource = dataSource;
-            mCountingEnabled = countingEnabled;
-        }
-
-        @Override
-        public void onResult(@NonNull List<Value> data, int position, int totalCount,
-                @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
-
-                // setup keys before dispatching data, so guaranteed to be ready
-                mDataSource.initKeys(previousPageKey, nextPageKey);
-
-                int trailingUnloadedCount = totalCount - position - data.size();
-                if (mCountingEnabled) {
-                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
-                            data, position, trailingUnloadedCount, 0));
-                } else {
-                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
-                }
-            }
-        }
-
-        @Override
-        public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
-                @Nullable Key nextPageKey) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                mDataSource.initKeys(previousPageKey, nextPageKey);
-                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
-            }
-        }
-    }
-
-    static class LoadCallbackImpl<Key, Value> extends LoadCallback<Key, Value> {
-        final LoadCallbackHelper<Value> mCallbackHelper;
-        private final PageKeyedDataSource<Key, Value> mDataSource;
-        LoadCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
-                @PageResult.ResultType int type, @Nullable Executor mainThreadExecutor,
-                @NonNull PageResult.Receiver<Value> receiver) {
-            mCallbackHelper = new LoadCallbackHelper<>(
-                    dataSource, type, mainThreadExecutor, receiver);
-            mDataSource = dataSource;
-        }
-
-        @Override
-        public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                if (mCallbackHelper.mResultType == PageResult.APPEND) {
-                    mDataSource.setNextKey(adjacentPageKey);
-                } else {
-                    mDataSource.setPreviousKey(adjacentPageKey);
-                }
-                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
-            }
-        }
-    }
-
-    @Nullable
-    @Override
-    final Key getKey(int position, Value item) {
-        // don't attempt to persist keys, since we currently don't pass them to initial load
-        return null;
-    }
-
-    @Override
-    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
-            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver) {
-        LoadInitialCallbackImpl<Key, Value> callback =
-                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
-        loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
-
-        // If initialLoad's callback is not called within the body, we force any following calls
-        // to post to the UI thread. This constructor may be run on a background thread, but
-        // after constructor, mutation must happen on UI thread.
-        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
-    }
-
-
-    @Override
-    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
-            int pageSize, @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver) {
-        @Nullable Key key = getNextKey();
-        if (key != null) {
-            loadAfter(new LoadParams<>(key, pageSize),
-                    new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
-        }
-    }
-
-    @Override
-    final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
-            int pageSize, @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<Value> receiver) {
-        @Nullable Key key = getPreviousKey();
-        if (key != null) {
-            loadBefore(new LoadParams<>(key, pageSize),
-                    new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
-        }
-    }
-
-    /**
-     * Load initial data.
-     * <p>
-     * This method is called first to initialize a PagedList with data. If it's possible to count
-     * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
-     * the callback via the three-parameter
-     * {@link LoadInitialCallback#onResult(List, int, int, Object, Object)}. This enables PagedLists
-     * presenting data from this source to display placeholders to represent unloaded items.
-     * <p>
-     * {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement, so it may be may be
-     * altered or ignored.
-     *
-     * @param params Parameters for initial load, including requested load size.
-     * @param callback Callback that receives initial load data.
-     */
-    public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
-            @NonNull LoadInitialCallback<Key, Value> callback);
-
-    /**
-     * Prepend page with the key specified by {@link LoadParams#key LoadParams.key}.
-     * <p>
-     * It's valid to return a different list size than the page size if it's easier, e.g. if your
-     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
-     * <p>
-     * Data may be passed synchronously during the load method, or deferred and called at a
-     * later time. Further loads going down will be blocked until the callback is called.
-     * <p>
-     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
-     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
-     * and prevent further loading.
-     *
-     * @param params Parameters for the load, including the key for the new page, and requested load
-     *               size.
-     * @param callback Callback that receives loaded data.
-     */
-    public abstract void loadBefore(@NonNull LoadParams<Key> params,
-            @NonNull LoadCallback<Key, Value> callback);
-
-    /**
-     * Append page with the key specified by {@link LoadParams#key LoadParams.key}.
-     * <p>
-     * It's valid to return a different list size than the page size if it's easier, e.g. if your
-     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
-     * <p>
-     * Data may be passed synchronously during the load method, or deferred and called at a
-     * later time. Further loads going down will be blocked until the callback is called.
-     * <p>
-     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
-     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
-     * and prevent further loading.
-     *
-     * @param params Parameters for the load, including the key for the new page, and requested load
-     *               size.
-     * @param callback Callback that receives loaded data.
-     */
-    public abstract void loadAfter(@NonNull LoadParams<Key> params,
-            @NonNull LoadCallback<Key, Value> callback);
-
-    @NonNull
-    @Override
-    public final <ToValue> PageKeyedDataSource<Key, ToValue> mapByPage(
-            @NonNull Function<List<Value>, List<ToValue>> function) {
-        return new WrapperPageKeyedDataSource<>(this, function);
-    }
-
-    @NonNull
-    @Override
-    public final <ToValue> PageKeyedDataSource<Key, ToValue> map(
-            @NonNull Function<Value, ToValue> function) {
-        return mapByPage(createListFunction(function));
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/PageResult.java b/paging/common/src/main/java/android/arch/paging/PageResult.java
deleted file mode 100644
index cf2216f..0000000
--- a/paging/common/src/main/java/android/arch/paging/PageResult.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.support.annotation.IntDef;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-
-import java.lang.annotation.Retention;
-import java.util.Collections;
-import java.util.List;
-
-class PageResult<T> {
-    @SuppressWarnings("unchecked")
-    private static final PageResult INVALID_RESULT =
-            new PageResult(Collections.EMPTY_LIST, 0);
-
-    @SuppressWarnings("unchecked")
-    static <T> PageResult<T> getInvalidResult() {
-        return INVALID_RESULT;
-    }
-
-
-    @Retention(SOURCE)
-    @IntDef({INIT, APPEND, PREPEND, TILE})
-    @interface ResultType {}
-
-    static final int INIT = 0;
-
-    // contiguous results
-    static final int APPEND = 1;
-    static final int PREPEND = 2;
-
-    // non-contiguous, tile result
-    static final int TILE = 3;
-
-    @NonNull
-    public final List<T> page;
-    @SuppressWarnings("WeakerAccess")
-    public final int leadingNulls;
-    @SuppressWarnings("WeakerAccess")
-    public final int trailingNulls;
-    @SuppressWarnings("WeakerAccess")
-    public final int positionOffset;
-
-    PageResult(@NonNull List<T> list, int leadingNulls, int trailingNulls, int positionOffset) {
-        this.page = list;
-        this.leadingNulls = leadingNulls;
-        this.trailingNulls = trailingNulls;
-        this.positionOffset = positionOffset;
-    }
-
-    PageResult(@NonNull List<T> list, int positionOffset) {
-        this.page = list;
-        this.leadingNulls = 0;
-        this.trailingNulls = 0;
-        this.positionOffset = positionOffset;
-    }
-
-    @Override
-    public String toString() {
-        return "Result " + leadingNulls
-                + ", " + page
-                + ", " + trailingNulls
-                + ", offset " + positionOffset;
-    }
-
-    public boolean isInvalid() {
-        return this == INVALID_RESULT;
-    }
-
-    abstract static class Receiver<T> {
-        @MainThread
-        public abstract void onPageResult(@ResultType int type, @NonNull PageResult<T> pageResult);
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/PagedList.java b/paging/common/src/main/java/android/arch/paging/PagedList.java
deleted file mode 100644
index a2ea3ab..0000000
--- a/paging/common/src/main/java/android/arch/paging/PagedList.java
+++ /dev/null
@@ -1,1005 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.AnyThread;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-
-import java.lang.ref.WeakReference;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Lazy loading list that pages in immutable content from a {@link DataSource}.
- * <p>
- * 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)}. To display a PagedList, see {@link PagedListAdapter}, which enables the
- * binding of a PagedList to a {@link android.support.v7.widget.RecyclerView}.
- * <h4>Loading Data</h4>
- * <p>
- * All data in a PagedList is loaded from its {@link DataSource}. Creating a PagedList loads the
- * first chunk of 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>
- * A PagedList initially presents this first partial load as its content, and expands over time as
- * content is loaded in. 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 LivePagedListBuilder} 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 does not count its data set in its initial load, or if  {@code false} is passed to
- * {@link Config.Builder#setEnablePlaceholders(boolean)} when building a {@link Config}.
- * <h4>Mutability and Snapshots</h4>
- * A PagedList is <em>mutable</em> while loading, or ready to load from its DataSource.
- * As loads succeed, a mutable PagedList will be updated via Runnables on the main thread. You can
- * listen to these updates with a {@link Callback}. (Note that {@link PagedListAdapter} will listen
- * to these to signal RecyclerView about the updates/changes).
- * <p>
- * If a PagedList attempts to load from an invalid DataSource, it will {@link #detach()}
- * from the DataSource, meaning that it will no longer attempt to load data. It will return true
- * from {@link #isImmutable()}, and a new DataSource / PagedList pair must be created to load
- * further data. See {@link DataSource} and {@link LivePagedListBuilder} for how new PagedLists are
- * created to represent changed data.
- * <p>
- * A PagedList snapshot is simply an immutable shallow copy of the current state of the PagedList as
- * a {@code List}. It will reference the same inner items, and contain the same {@code null}
- * placeholders, if present.
- *
- * @param <T> The type of the entries in the list.
- */
-public abstract class PagedList<T> extends AbstractList<T> {
-    @NonNull
-    final Executor mMainThreadExecutor;
-    @NonNull
-    final Executor mBackgroundThreadExecutor;
-    @Nullable
-    final BoundaryCallback<T> mBoundaryCallback;
-    @NonNull
-    final Config mConfig;
-    @NonNull
-    final PagedStorage<T> mStorage;
-
-    int mLastLoad = 0;
-    T mLastItem = null;
-
-    // if set to true, mBoundaryCallback is non-null, and should
-    // be dispatched when nearby load has occurred
-    private boolean mBoundaryCallbackBeginDeferred = false;
-    private boolean mBoundaryCallbackEndDeferred = false;
-
-    // lowest and highest index accessed by loadAround. Used to
-    // decide when mBoundaryCallback should be dispatched
-    private int mLowestIndexAccessed = Integer.MAX_VALUE;
-    private int mHighestIndexAccessed = Integer.MIN_VALUE;
-
-    private final AtomicBoolean mDetached = new AtomicBoolean(false);
-
-    protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
-
-    PagedList(@NonNull PagedStorage<T> storage,
-            @NonNull Executor mainThreadExecutor,
-            @NonNull Executor backgroundThreadExecutor,
-            @Nullable BoundaryCallback<T> boundaryCallback,
-            @NonNull Config config) {
-        mStorage = storage;
-        mMainThreadExecutor = mainThreadExecutor;
-        mBackgroundThreadExecutor = backgroundThreadExecutor;
-        mBoundaryCallback = boundaryCallback;
-        mConfig = config;
-    }
-
-    /**
-     * Create a PagedList which loads data from the provided data source on a background thread,
-     * posting updates to the main thread.
-     *
-     *
-     * @param dataSource DataSource providing data to the PagedList
-     * @param mainThreadExecutor Thread that will use and consume data from the PagedList.
-     *                           Generally, this is the UI/main thread.
-     * @param backgroundThreadExecutor Data loading will be done via this executor - should be a
-     *                                 background thread.
-     * @param boundaryCallback Optional boundary callback to attach to the list.
-     * @param config PagedList Config, which defines how the PagedList will load data.
-     * @param <K> Key type that indicates to the DataSource what data to load.
-     * @param <T> Type of items to be held and loaded by the PagedList.
-     *
-     * @return Newly created PagedList, which will page in data from the DataSource as needed.
-     */
-    @NonNull
-    private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
-            @NonNull Executor mainThreadExecutor,
-            @NonNull Executor backgroundThreadExecutor,
-            @Nullable BoundaryCallback<T> boundaryCallback,
-            @NonNull Config config,
-            @Nullable K key) {
-        if (dataSource.isContiguous() || !config.enablePlaceholders) {
-            int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
-            if (!dataSource.isContiguous()) {
-                //noinspection unchecked
-                dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
-                        .wrapAsContiguousWithoutPlaceholders();
-                if (key != null) {
-                    lastLoad = (int) key;
-                }
-            }
-            ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
-            return new ContiguousPagedList<>(contigDataSource,
-                    mainThreadExecutor,
-                    backgroundThreadExecutor,
-                    boundaryCallback,
-                    config,
-                    key,
-                    lastLoad);
-        } else {
-            return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
-                    mainThreadExecutor,
-                    backgroundThreadExecutor,
-                    boundaryCallback,
-                    config,
-                    (key != null) ? (Integer) key : 0);
-        }
-    }
-
-    /**
-     * Builder class for PagedList.
-     * <p>
-     * DataSource, Config, main thread and background executor must all be provided.
-     * <p>
-     * A PagedList queries initial data from its DataSource during construction, to avoid empty
-     * PagedLists being presented to the UI when possible. It's preferred 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>
-     * {@link LivePagedListBuilder} 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.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static final class Builder<Key, Value> {
-        private final DataSource<Key, Value> mDataSource;
-        private final Config mConfig;
-        private Executor mMainThreadExecutor;
-        private Executor mBackgroundThreadExecutor;
-        private BoundaryCallback mBoundaryCallback;
-        private Key mInitialKey;
-
-        /**
-         * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}.
-         *
-         * @param dataSource DataSource the PagedList will load from.
-         * @param config Config that defines how the PagedList loads data from its DataSource.
-         */
-        public Builder(@NonNull DataSource<Key, Value> dataSource, @NonNull Config config) {
-            //noinspection ConstantConditions
-            if (dataSource == null) {
-                throw new IllegalArgumentException("DataSource may not be null");
-            }
-            //noinspection ConstantConditions
-            if (config == null) {
-                throw new IllegalArgumentException("Config may not be null");
-            }
-            mDataSource = dataSource;
-            mConfig = config;
-        }
-
-        /**
-         * Create a PagedList.Builder with the provided {@link DataSource} and page size.
-         * <p>
-         * This method is a convenience for:
-         * <pre>
-         * PagedList.Builder(dataSource,
-         *         new PagedList.Config.Builder().setPageSize(pageSize).build());
-         * </pre>
-         *
-         * @param dataSource DataSource the PagedList will load from.
-         * @param pageSize Config that defines how the PagedList loads data from its DataSource.
-         */
-        public Builder(@NonNull DataSource<Key, Value> dataSource, int pageSize) {
-            this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build());
-        }
-        /**
-         * The executor defining where main/UI thread for page loading updates.
-         *
-         * @param mainThreadExecutor Executor for main/UI thread to receive {@link Callback} calls.
-         * @return this
-         */
-        @NonNull
-        public Builder<Key, Value> setMainThreadExecutor(@NonNull Executor mainThreadExecutor) {
-            mMainThreadExecutor = mainThreadExecutor;
-            return this;
-        }
-
-        /**
-         * The executor on which background loading will be run.
-         * <p>
-         * Does not affect initial load, which will be done on whichever thread the PagedList is
-         * created on.
-         *
-         * @param backgroundThreadExecutor Executor for background DataSource loading.
-         * @return this
-         */
-        @NonNull
-        public Builder<Key, Value> setBackgroundThreadExecutor(
-                @NonNull Executor backgroundThreadExecutor) {
-            mBackgroundThreadExecutor = backgroundThreadExecutor;
-            return this;
-        }
-
-        /**
-         * The BoundaryCallback for out of data events.
-         * <p>
-         * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load.
-         *
-         * @param boundaryCallback BoundaryCallback for listening to out-of-data events.
-         * @return this
-         */
-        @SuppressWarnings("unused")
-        @NonNull
-        public Builder<Key, Value> setBoundaryCallback(
-                @Nullable BoundaryCallback boundaryCallback) {
-            mBoundaryCallback = boundaryCallback;
-            return this;
-        }
-
-        /**
-         * Sets the initial key the DataSource should load around as part of initialization.
-         *
-         * @param initialKey Key the DataSource should load around as part of initialization.
-         * @return this
-         */
-        @NonNull
-        public Builder<Key, Value> setInitialKey(@Nullable Key initialKey) {
-            mInitialKey = initialKey;
-            return this;
-        }
-
-        /**
-         * Creates a {@link PagedList} with the given parameters.
-         * <p>
-         * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a
-         * DataSource posts all of its work (e.g. to a network thread), the PagedList will
-         * be immediately created as empty, and grow to its initial size when the initial load
-         * completes.
-         * <p>
-         * If the DataSource implements its load synchronously, doing the load work immediately in
-         * the loadInitial method, the PagedList will block on that load before completing
-         * construction. In this case, use a background thread to create a PagedList.
-         * <p>
-         * It's fine to create a PagedList with an async DataSource on the main thread, such as in
-         * the constructor of a ViewModel. An async network load won't block the initialLoad
-         * function. For a synchronous DataSource such as one created from a Room database, a
-         * {@code LiveData<PagedList>} can be safely constructed with {@link LivePagedListBuilder}
-         * on the main thread, since actual construction work is deferred, and done on a background
-         * 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
-         */
-        @WorkerThread
-        @NonNull
-        public PagedList<Value> build() {
-            // TODO: define defaults, once they can be used in module without android dependency
-            if (mMainThreadExecutor == null) {
-                throw new IllegalArgumentException("MainThreadExecutor required");
-            }
-            if (mBackgroundThreadExecutor == null) {
-                throw new IllegalArgumentException("BackgroundThreadExecutor required");
-            }
-
-            //noinspection unchecked
-            return PagedList.create(
-                    mDataSource,
-                    mMainThreadExecutor,
-                    mBackgroundThreadExecutor,
-                    mBoundaryCallback,
-                    mConfig,
-                    mInitialKey);
-        }
-    }
-
-    /**
-     * Get the item in the list of loaded items at the provided index.
-     *
-     * @param index Index in the loaded item list. Must be >= 0, and &lt; {@link #size()}
-     * @return The item at the passed index, or null if a null placeholder is at the specified
-     *         position.
-     *
-     * @see #size()
-     */
-    @Override
-    @Nullable
-    public T get(int index) {
-        T item = mStorage.get(index);
-        if (item != null) {
-            mLastItem = item;
-        }
-        return item;
-    }
-
-    /**
-     * Load adjacent items to passed index.
-     *
-     * @param index Index at which to load.
-     */
-    public void loadAround(int index) {
-        mLastLoad = index + getPositionOffset();
-        loadAroundInternal(index);
-
-        mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
-        mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
-
-        /*
-         * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
-         * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
-         * and accesses happen near the boundaries.
-         *
-         * Note: we post here, since RecyclerView may want to add items in response, and this
-         * call occurs in PagedListAdapter bind.
-         */
-        tryDispatchBoundaryCallbacks(true);
-    }
-
-    // Creation thread for initial synchronous load, otherwise main thread
-    // Safe to access main thread only state - no other thread has reference during construction
-    @AnyThread
-    void deferBoundaryCallbacks(final boolean deferEmpty,
-            final boolean deferBegin, final boolean deferEnd) {
-        if (mBoundaryCallback == null) {
-            throw new IllegalStateException("Computing boundary");
-        }
-
-        /*
-         * If lowest/highest haven't been initialized, set them to storage size,
-         * since placeholders must already be computed by this point.
-         *
-         * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately
-         * if the initial load size is smaller than the prefetch window (see
-         * TiledPagedListTest#boundaryCallback_immediate())
-         */
-        if (mLowestIndexAccessed == Integer.MAX_VALUE) {
-            mLowestIndexAccessed = mStorage.size();
-        }
-        if (mHighestIndexAccessed == Integer.MIN_VALUE) {
-            mHighestIndexAccessed = 0;
-        }
-
-        if (deferEmpty || deferBegin || deferEnd) {
-            // Post to the main thread, since we may be on creation thread currently
-            mMainThreadExecutor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    // on is dispatched immediately, since items won't be accessed
-                    //noinspection ConstantConditions
-                    if (deferEmpty) {
-                        mBoundaryCallback.onZeroItemsLoaded();
-                    }
-
-                    // for other callbacks, mark deferred, and only dispatch if loadAround
-                    // has been called near to the position
-                    if (deferBegin) {
-                        mBoundaryCallbackBeginDeferred = true;
-                    }
-                    if (deferEnd) {
-                        mBoundaryCallbackEndDeferred = true;
-                    }
-                    tryDispatchBoundaryCallbacks(false);
-                }
-            });
-        }
-    }
-
-    /**
-     * Call this when mLowest/HighestIndexAccessed are changed, or
-     * mBoundaryCallbackBegin/EndDeferred is set.
-     */
-    private void tryDispatchBoundaryCallbacks(boolean post) {
-        final boolean dispatchBegin = mBoundaryCallbackBeginDeferred
-                && mLowestIndexAccessed <= mConfig.prefetchDistance;
-        final boolean dispatchEnd = mBoundaryCallbackEndDeferred
-                && mHighestIndexAccessed >= size() - mConfig.prefetchDistance;
-
-        if (!dispatchBegin && !dispatchEnd) {
-            return;
-        }
-
-        if (dispatchBegin) {
-            mBoundaryCallbackBeginDeferred = false;
-        }
-        if (dispatchEnd) {
-            mBoundaryCallbackEndDeferred = false;
-        }
-        if (post) {
-            mMainThreadExecutor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
-                }
-            });
-        } else {
-            dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
-        }
-    }
-
-    private void dispatchBoundaryCallbacks(boolean begin, boolean end) {
-        // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
-        if (begin) {
-            //noinspection ConstantConditions
-            mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem());
-        }
-        if (end) {
-            //noinspection ConstantConditions
-            mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem());
-        }
-    }
-
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    void offsetBoundaryAccessIndices(int offset) {
-        mLowestIndexAccessed += offset;
-        mHighestIndexAccessed += offset;
-    }
-
-    /**
-     * Returns size of the list, including any not-yet-loaded null padding.
-     *
-     * @return Current total size of the list.
-     */
-    @Override
-    public int size() {
-        return mStorage.size();
-    }
-
-    /**
-     * Returns whether the list is immutable.
-     *
-     * Immutable lists may not become mutable again, and may safely be accessed from any thread.
-     * <p>
-     * In the future, this method may return true when a PagedList has completed loading from its
-     * DataSource. Currently, it is equivalent to {@link #isDetached()}.
-     *
-     * @return True if the PagedList is immutable.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public boolean isImmutable() {
-        return isDetached();
-    }
-
-    /**
-     * Returns an immutable snapshot of the PagedList in its current state.
-     *
-     * If this PagedList {@link #isImmutable() is immutable} due to its DataSource being invalid, it
-     * will be returned.
-     *
-     * @return Immutable snapshot of PagedList data.
-     */
-    @SuppressWarnings("WeakerAccess")
-    @NonNull
-    public List<T> snapshot() {
-        if (isImmutable()) {
-            return this;
-        }
-        return new SnapshotPagedList<>(this);
-    }
-
-    abstract boolean isContiguous();
-
-    /**
-     * Return the Config used to construct this PagedList.
-     *
-     * @return the Config of this PagedList
-     */
-    @NonNull
-    public Config getConfig() {
-        return mConfig;
-    }
-
-    /**
-     * Return the DataSource that provides data to this PagedList.
-     *
-     * @return the DataSource of this PagedList.
-     */
-    @NonNull
-    public abstract DataSource<?, T> getDataSource();
-
-    /**
-     * Return the key for the position passed most recently to {@link #loadAround(int)}.
-     * <p>
-     * When a PagedList is invalidated, you can pass the key returned by this function to initialize
-     * the next PagedList. This ensures (depending on load times) that the next PagedList that
-     * arrives will have data that overlaps. If you use {@link LivePagedListBuilder}, it will do
-     * this for you.
-     *
-     * @return Key of position most recently passed to {@link #loadAround(int)}.
-     */
-    @Nullable
-    public abstract Object getLastKey();
-
-    /**
-     * True if the PagedList has detached the DataSource it was loading from, and will no longer
-     * load new data.
-     * <p>
-     * A detached list is {@link #isImmutable() immutable}.
-     *
-     * @return True if the data source is detached.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public boolean isDetached() {
-        return mDetached.get();
-    }
-
-    /**
-     * Detach the PagedList from its DataSource, and attempt to load no more data.
-     * <p>
-     * This is called automatically when a DataSource load returns <code>null</code>, which is a
-     * signal to stop loading. The PagedList will continue to present existing data, but will not
-     * initiate new loads.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public void detach() {
-        mDetached.set(true);
-    }
-
-    /**
-     * Position offset of the data in the list.
-     * <p>
-     * If data is supplied by a {@link PositionalDataSource}, the item returned from
-     * <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>.
-     * <p>
-     * If the DataSource is a {@link ItemKeyedDataSource} or {@link PageKeyedDataSource}, it
-     * doesn't use positions, returns 0.
-     */
-    public int getPositionOffset() {
-        return mStorage.getPositionOffset();
-    }
-
-    /**
-     * Adds a callback, and issues updates since the previousSnapshot was created.
-     * <p>
-     * If previousSnapshot is passed, the callback will also immediately be dispatched any
-     * differences between the previous snapshot, and the current state. For example, if the
-     * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls,
-     * 12 items, 3 nulls, the callback would immediately receive a call of
-     * <code>onChanged(14, 2)</code>.
-     * <p>
-     * This allows an observer that's currently presenting a snapshot to catch up to the most recent
-     * version, including any changes that may have been made.
-     * <p>
-     * The callback is internally held as weak reference, so PagedList doesn't hold a strong
-     * reference to its observer, such as a {@link PagedListAdapter}. If an adapter were held with a
-     * strong reference, it would be necessary to clear its PagedList observer before it could be
-     * GC'd.
-     *
-     * @param previousSnapshot Snapshot previously captured from this List, or null.
-     * @param callback Callback to dispatch to.
-     *
-     * @see #removeWeakCallback(Callback)
-     */
-    @SuppressWarnings("WeakerAccess")
-    public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
-        if (previousSnapshot != null && previousSnapshot != this) {
-
-            if (previousSnapshot.isEmpty()) {
-                if (!mStorage.isEmpty()) {
-                    // If snapshot is empty, diff is trivial - just notify number new items.
-                    // Note: occurs in async init, when snapshot taken before init page arrives
-                    callback.onInserted(0, mStorage.size());
-                }
-            } else {
-                PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
-
-                //noinspection unchecked
-                dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
-            }
-        }
-
-        // first, clean up any empty weak refs
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            Callback currentCallback = mCallbacks.get(i).get();
-            if (currentCallback == null) {
-                mCallbacks.remove(i);
-            }
-        }
-
-        // then add the new one
-        mCallbacks.add(new WeakReference<>(callback));
-    }
-    /**
-     * Removes a previously added callback.
-     *
-     * @param callback Callback, previously added.
-     * @see #addWeakCallback(List, Callback)
-     */
-    @SuppressWarnings("WeakerAccess")
-    public void removeWeakCallback(@NonNull Callback callback) {
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            Callback currentCallback = mCallbacks.get(i).get();
-            if (currentCallback == null || currentCallback == callback) {
-                // found callback, or empty weak ref
-                mCallbacks.remove(i);
-            }
-        }
-    }
-
-    void notifyInserted(int position, int count) {
-        if (count != 0) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                Callback callback = mCallbacks.get(i).get();
-                if (callback != null) {
-                    callback.onInserted(position, count);
-                }
-            }
-        }
-    }
-
-    void notifyChanged(int position, int count) {
-        if (count != 0) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                Callback callback = mCallbacks.get(i).get();
-
-                if (callback != null) {
-                    callback.onChanged(position, count);
-                }
-            }
-        }
-    }
-
-
-
-    /**
-     * Dispatch updates since the non-empty snapshot was taken.
-     *
-     * @param snapshot Non-empty snapshot.
-     * @param callback Callback for updates that have occurred since snapshot.
-     */
-    abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
-            @NonNull Callback callback);
-
-    abstract void loadAroundInternal(int index);
-
-    /**
-     * Callback signaling when content is loaded into the list.
-     * <p>
-     * Can be used to listen to items being paged in and out. These calls will be dispatched on
-     * the executor defined by {@link Builder#setMainThreadExecutor(Executor)}, which defaults to
-     * the main/UI thread.
-     */
-    public abstract static class Callback {
-        /**
-         * Called when null padding items have been loaded to signal newly available data, or when
-         * data that hasn't been used in a while has been dropped, and swapped back to null.
-         *
-         * @param position Position of first newly loaded items, out of total number of items
-         *                 (including padded nulls).
-         * @param count    Number of items loaded.
-         */
-        public abstract void onChanged(int position, int count);
-
-        /**
-         * Called when new items have been loaded at the end or beginning of the list.
-         *
-         * @param position Position of the first newly loaded item (in practice, either
-         *                 <code>0</code> or <code>size - 1</code>.
-         * @param count    Number of items loaded.
-         */
-        public abstract void onInserted(int position, int count);
-
-        /**
-         * Called when items have been removed at the end or beginning of the list, and have not
-         * been replaced by padded nulls.
-         *
-         * @param position Position of the first newly loaded item (in practice, either
-         *                 <code>0</code> or <code>size - 1</code>.
-         * @param count    Number of items loaded.
-         */
-        @SuppressWarnings("unused")
-        public abstract void onRemoved(int position, int count);
-    }
-
-    /**
-     * Configures how a PagedList loads content from its DataSource.
-     * <p>
-     * Use a Config {@link Builder} to construct and define custom loading behavior, such as
-     * {@link Builder#setPageSize(int)}, which defines number of items loaded at a time}.
-     */
-    public static class Config {
-        /**
-         * Size of each page loaded by the PagedList.
-         */
-        public final int pageSize;
-
-        /**
-         * Prefetch distance which defines how far ahead to load.
-         * <p>
-         * If this value is set to 50, the paged list will attempt to load 50 items in advance of
-         * data that's already been accessed.
-         *
-         * @see PagedList#loadAround(int)
-         */
-        @SuppressWarnings("WeakerAccess")
-        public final int prefetchDistance;
-
-        /**
-         * Defines whether the PagedList may display null placeholders, if the DataSource provides
-         * them.
-         */
-        @SuppressWarnings("WeakerAccess")
-        public final boolean enablePlaceholders;
-
-        /**
-         * Size hint for initial load of PagedList, often larger than a regular page.
-         */
-        @SuppressWarnings("WeakerAccess")
-        public final int initialLoadSizeHint;
-
-        private Config(int pageSize, int prefetchDistance,
-                boolean enablePlaceholders, int initialLoadSizeHint) {
-            this.pageSize = pageSize;
-            this.prefetchDistance = prefetchDistance;
-            this.enablePlaceholders = enablePlaceholders;
-            this.initialLoadSizeHint = initialLoadSizeHint;
-        }
-
-        /**
-         * Builder class for {@link Config}.
-         * <p>
-         * You must at minimum specify page size with {@link #setPageSize(int)}.
-         */
-        public static final class Builder {
-            private int mPageSize = -1;
-            private int mPrefetchDistance = -1;
-            private int mInitialLoadSizeHint = -1;
-            private boolean mEnablePlaceholders = true;
-
-            /**
-             * 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
-             */
-            public Builder setPageSize(int pageSize) {
-                this.mPageSize = pageSize;
-                return this;
-            }
-
-            /**
-             * Defines how far from the edge of loaded content an access must be to trigger further
-             * 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
-             */
-            public Builder setPrefetchDistance(int prefetchDistance) {
-                this.mPrefetchDistance = prefetchDistance;
-                return this;
-            }
-
-            /**
-             * Pass false to disable null placeholders in PagedLists using this Config.
-             * <p>
-             * 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).
-             * <p>
-             * 2) placeholders are not disabled on the Config.
-             * <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
-             */
-            @SuppressWarnings("SameParameterValue")
-            public Builder setEnablePlaceholders(boolean enablePlaceholders) {
-                this.mEnablePlaceholders = enablePlaceholders;
-                return this;
-            }
-
-            /**
-             * Defines how many items to load when first load occurs.
-             * <p>
-             * This value is typically larger than page size, so on first load data there's a large
-             * enough range of content loaded to cover small scrolls.
-             * <p>
-             * When using a {@link PositionalDataSource}, the initial load size will be coerced to
-             * an integer multiple of pageSize, to enable efficient tiling.
-             * <p>
-             * If not set, defaults to three times page size.
-             *
-             * @param initialLoadSizeHint Number of items to load while initializing the PagedList.
-             * @return this
-             */
-            @SuppressWarnings("WeakerAccess")
-            public Builder setInitialLoadSizeHint(int initialLoadSizeHint) {
-                this.mInitialLoadSizeHint = initialLoadSizeHint;
-                return this;
-            }
-
-            /**
-             * Creates a {@link Config} with the given parameters.
-             *
-             * @return A new Config.
-             */
-            public Config build() {
-                if (mPageSize < 1) {
-                    throw new IllegalArgumentException("Page size must be a positive number");
-                }
-                if (mPrefetchDistance < 0) {
-                    mPrefetchDistance = mPageSize;
-                }
-                if (mInitialLoadSizeHint < 0) {
-                    mInitialLoadSizeHint = mPageSize * 3;
-                }
-                if (!mEnablePlaceholders && mPrefetchDistance == 0) {
-                    throw new IllegalArgumentException("Placeholders and prefetch are the only ways"
-                            + " to trigger loading of more data in the PagedList, so either"
-                            + " placeholders must be enabled, or prefetch distance must be > 0.");
-                }
-
-                return new Config(mPageSize, mPrefetchDistance,
-                        mEnablePlaceholders, mInitialLoadSizeHint);
-            }
-        }
-    }
-
-    /**
-     * Signals when a PagedList has reached the end of available data.
-     * <p>
-     * When local storage is a cache of network data, it's common to set up a streaming pipeline:
-     * Network data is paged into the database, database is paged into UI. Paging from the database
-     * to UI can be done with a {@code LiveData<PagedList>}, but it's still necessary to know when
-     * to trigger network loads.
-     * <p>
-     * BoundaryCallback does this signaling - when a DataSource runs out of data at the end of
-     * the list, {@link #onItemAtEndLoaded(Object)} is called, and you can start an async network
-     * load that will write the result directly to the database. Because the database is being
-     * observed, the UI bound to the {@code LiveData<PagedList>} will update automatically to
-     * account for the new items.
-     * <p>
-     * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
-     * {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple
-     * times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should
-     * avoid triggering it again while the load is ongoing.
-     * <p>
-     * BoundaryCallback only passes the item at front or end of the list. Number of items is not
-     * passed, since it may not be fully computed by the DataSource if placeholders are not
-     * supplied. Keys are not known because the BoundaryCallback is independent of the
-     * DataSource-specific keys, which may be different for local vs remote storage.
-     * <p>
-     * The database + network Repository in the
-     * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
-     * shows how to implement a network BoundaryCallback using
-     * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
-     * handling swipe-to-refresh, network errors, and retry.
-     *
-     * @param <T> Type loaded by the PagedList.
-     */
-    @MainThread
-    public abstract static class BoundaryCallback<T> {
-        /**
-         * Called when zero items are returned from an initial load of the PagedList's data source.
-         */
-        public void onZeroItemsLoaded() {}
-
-        /**
-         * Called when the item at the front of the PagedList has been loaded, and access has
-         * occurred within {@link Config#prefetchDistance} of it.
-         * <p>
-         * No more data will be prepended to the PagedList before this item.
-         *
-         * @param itemAtFront The first item of PagedList
-         */
-        public void onItemAtFrontLoaded(@NonNull T itemAtFront) {}
-
-        /**
-         * Called when the item at the end of the PagedList has been loaded, and access has
-         * occurred within {@link Config#prefetchDistance} of it.
-         * <p>
-         * No more data will be appended to the PagedList after this item.
-         *
-         * @param itemAtEnd The first item of PagedList
-         */
-        public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/PagedStorage.java b/paging/common/src/main/java/android/arch/paging/PagedStorage.java
deleted file mode 100644
index d4531d3..0000000
--- a/paging/common/src/main/java/android/arch/paging/PagedStorage.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
-
-final class PagedStorage<T> extends AbstractList<T> {
-    /**
-     * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item
-     * in that position is already loading. We use a singleton placeholder list that is distinct
-     * from Collections.EMPTY_LIST for safety.
-     */
-    @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
-    private static final List PLACEHOLDER_LIST = new ArrayList();
-
-    // Always set
-    private int mLeadingNullCount;
-    /**
-     * List of pages in storage.
-     *
-     * Two storage modes:
-     *
-     * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled().
-     *     Safe to access any item in any page.
-     *
-     * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
-     *     mPages may have nulls, or placeholder (empty) pages while content is loading.
-     */
-    private final ArrayList<List<T>> mPages;
-    private int mTrailingNullCount;
-
-    private int mPositionOffset;
-    /**
-     * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in
-     * {@link #mPages} may be null, but this value still counts them.
-     */
-    private int mStorageCount;
-
-    // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set
-    private int mPageSize;
-
-    private int mNumberPrepended;
-    private int mNumberAppended;
-
-    PagedStorage() {
-        mLeadingNullCount = 0;
-        mPages = new ArrayList<>();
-        mTrailingNullCount = 0;
-        mPositionOffset = 0;
-        mStorageCount = 0;
-        mPageSize = 1;
-        mNumberPrepended = 0;
-        mNumberAppended = 0;
-    }
-
-    PagedStorage(int leadingNulls, List<T> page, int trailingNulls) {
-        this();
-        init(leadingNulls, page, trailingNulls, 0);
-    }
-
-    private PagedStorage(PagedStorage<T> other) {
-        mLeadingNullCount = other.mLeadingNullCount;
-        mPages = new ArrayList<>(other.mPages);
-        mTrailingNullCount = other.mTrailingNullCount;
-        mPositionOffset = other.mPositionOffset;
-        mStorageCount = other.mStorageCount;
-        mPageSize = other.mPageSize;
-        mNumberPrepended = other.mNumberPrepended;
-        mNumberAppended = other.mNumberAppended;
-    }
-
-    PagedStorage<T> snapshot() {
-        return new PagedStorage<>(this);
-    }
-
-    private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
-        mLeadingNullCount = leadingNulls;
-        mPages.clear();
-        mPages.add(page);
-        mTrailingNullCount = trailingNulls;
-
-        mPositionOffset = positionOffset;
-        mStorageCount = page.size();
-
-        // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
-        // even if it will break if nulls convert.
-        mPageSize = page.size();
-
-        mNumberPrepended = 0;
-        mNumberAppended = 0;
-    }
-
-    void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
-            @NonNull Callback callback) {
-        init(leadingNulls, page, trailingNulls, positionOffset);
-        callback.onInitialized(size());
-    }
-
-    @Override
-    public T get(int i) {
-        if (i < 0 || i >= size()) {
-            throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
-        }
-
-        // is it definitely outside 'mPages'?
-        int localIndex = i - mLeadingNullCount;
-        if (localIndex < 0 || localIndex >= mStorageCount) {
-            return null;
-        }
-
-        int localPageIndex;
-        int pageInternalIndex;
-
-        if (isTiled()) {
-            // it's inside mPages, and we're tiled. Jump to correct tile.
-            localPageIndex = localIndex / mPageSize;
-            pageInternalIndex = localIndex % mPageSize;
-        } else {
-            // it's inside mPages, but page sizes aren't regular. Walk to correct tile.
-            // Pages can only be null while tiled, so accessing page count is safe.
-            pageInternalIndex = localIndex;
-            final int localPageCount = mPages.size();
-            for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
-                int pageSize = mPages.get(localPageIndex).size();
-                if (pageSize > pageInternalIndex) {
-                    // stop, found the page
-                    break;
-                }
-                pageInternalIndex -= pageSize;
-            }
-        }
-
-        List<T> page = mPages.get(localPageIndex);
-        if (page == null || page.size() == 0) {
-            // can only occur in tiled case, with untouched inner/placeholder pages
-            return null;
-        }
-        return page.get(pageInternalIndex);
-    }
-
-    /**
-     * Returns true if all pages are the same size, except for the last, which may be smaller
-     */
-    boolean isTiled() {
-        return mPageSize > 0;
-    }
-
-    int getLeadingNullCount() {
-        return mLeadingNullCount;
-    }
-
-    int getTrailingNullCount() {
-        return mTrailingNullCount;
-    }
-
-    int getStorageCount() {
-        return mStorageCount;
-    }
-
-    int getNumberAppended() {
-        return mNumberAppended;
-    }
-
-    int getNumberPrepended() {
-        return mNumberPrepended;
-    }
-
-    int getPageCount() {
-        return mPages.size();
-    }
-
-    interface Callback {
-        void onInitialized(int count);
-        void onPagePrepended(int leadingNulls, int changed, int added);
-        void onPageAppended(int endPosition, int changed, int added);
-        void onPagePlaceholderInserted(int pageIndex);
-        void onPageInserted(int start, int count);
-    }
-
-    int getPositionOffset() {
-        return mPositionOffset;
-    }
-
-    @Override
-    public int size() {
-        return mLeadingNullCount + mStorageCount + mTrailingNullCount;
-    }
-
-    int computeLeadingNulls() {
-        int total = mLeadingNullCount;
-        final int pageCount = mPages.size();
-        for (int i = 0; i < pageCount; i++) {
-            List page = mPages.get(i);
-            if (page != null && page != PLACEHOLDER_LIST) {
-                break;
-            }
-            total += mPageSize;
-        }
-        return total;
-    }
-
-    int computeTrailingNulls() {
-        int total = mTrailingNullCount;
-        for (int i = mPages.size() - 1; i >= 0; i--) {
-            List page = mPages.get(i);
-            if (page != null && page != PLACEHOLDER_LIST) {
-                break;
-            }
-            total += mPageSize;
-        }
-        return total;
-    }
-
-    // ---------------- Contiguous API -------------------
-
-    T getFirstLoadedItem() {
-        // safe to access first page's first item here:
-        // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
-        return mPages.get(0).get(0);
-    }
-
-    T getLastLoadedItem() {
-        // safe to access last page's last item here:
-        // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
-        List<T> page = mPages.get(mPages.size() - 1);
-        return page.get(page.size() - 1);
-    }
-
-    void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
-        final int count = page.size();
-        if (count == 0) {
-            // Nothing returned from source, stop loading in this direction
-            return;
-        }
-        if (mPageSize > 0 && count != mPageSize) {
-            if (mPages.size() == 1 && count > mPageSize) {
-                // prepending to a single item - update current page size to that of 'inner' page
-                mPageSize = count;
-            } else {
-                // no longer tiled
-                mPageSize = -1;
-            }
-        }
-
-        mPages.add(0, page);
-        mStorageCount += count;
-
-        final int changedCount = Math.min(mLeadingNullCount, count);
-        final int addedCount = count - changedCount;
-
-        if (changedCount != 0) {
-            mLeadingNullCount -= changedCount;
-        }
-        mPositionOffset -= addedCount;
-        mNumberPrepended += count;
-
-        callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
-    }
-
-    void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
-        final int count = page.size();
-        if (count == 0) {
-            // Nothing returned from source, stop loading in this direction
-            return;
-        }
-
-        if (mPageSize > 0) {
-            // if the previous page was smaller than mPageSize,
-            // or if this page is larger than the previous, disable tiling
-            if (mPages.get(mPages.size() - 1).size() != mPageSize
-                    || count > mPageSize) {
-                mPageSize = -1;
-            }
-        }
-
-        mPages.add(page);
-        mStorageCount += count;
-
-        final int changedCount = Math.min(mTrailingNullCount, count);
-        final int addedCount = count - changedCount;
-
-        if (changedCount != 0) {
-            mTrailingNullCount -= changedCount;
-        }
-        mNumberAppended += count;
-        callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
-                changedCount, addedCount);
-    }
-
-    // ------------------ Non-Contiguous API (tiling required) ----------------------
-
-    void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
-            int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
-
-        int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize;
-        for (int i = 0; i < pageCount; i++) {
-            int beginInclusive = i * pageSize;
-            int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize);
-
-            List<T> sublist = multiPageList.subList(beginInclusive, endExclusive);
-
-            if (i == 0) {
-                // Trailing nulls for first page includes other pages in multiPageList
-                int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size();
-                init(leadingNulls, sublist, initialTrailingNulls, positionOffset);
-            } else {
-                int insertPosition = leadingNulls + beginInclusive;
-                insertPage(insertPosition, sublist, null);
-            }
-        }
-        callback.onInitialized(size());
-    }
-
-    public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) {
-        final int newPageSize = page.size();
-        if (newPageSize != mPageSize) {
-            // differing page size is OK in 2 cases, when the page is being added:
-            // 1) to the end (in which case, ignore new smaller size)
-            // 2) only the last page has been added so far (in which case, adopt new bigger size)
-
-            int size = size();
-            boolean addingLastPage = position == (size - size % mPageSize)
-                    && newPageSize < mPageSize;
-            boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1
-                    && newPageSize > mPageSize;
-
-            // OK only if existing single page, and it's the last one
-            if (!onlyEndPagePresent && !addingLastPage) {
-                throw new IllegalArgumentException("page introduces incorrect tiling");
-            }
-            if (onlyEndPagePresent) {
-                mPageSize = newPageSize;
-            }
-        }
-
-        int pageIndex = position / mPageSize;
-
-        allocatePageRange(pageIndex, pageIndex);
-
-        int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
-
-        List<T> oldPage = mPages.get(localPageIndex);
-        if (oldPage != null && oldPage != PLACEHOLDER_LIST) {
-            throw new IllegalArgumentException(
-                    "Invalid position " + position + ": data already loaded");
-        }
-        mPages.set(localPageIndex, page);
-        if (callback != null) {
-            callback.onPageInserted(position, page.size());
-        }
-    }
-
-    private void allocatePageRange(final int minimumPage, final int maximumPage) {
-        int leadingNullPages = mLeadingNullCount / mPageSize;
-
-        if (minimumPage < leadingNullPages) {
-            for (int i = 0; i < leadingNullPages - minimumPage; i++) {
-                mPages.add(0, null);
-            }
-            int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize;
-            mStorageCount += newStorageAllocated;
-            mLeadingNullCount -= newStorageAllocated;
-
-            leadingNullPages = minimumPage;
-        }
-        if (maximumPage >= leadingNullPages + mPages.size()) {
-            int newStorageAllocated = Math.min(mTrailingNullCount,
-                    (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize);
-            for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) {
-                mPages.add(mPages.size(), null);
-            }
-            mStorageCount += newStorageAllocated;
-            mTrailingNullCount -= newStorageAllocated;
-        }
-    }
-
-    public void allocatePlaceholders(int index, int prefetchDistance,
-            int pageSize, Callback callback) {
-        if (pageSize != mPageSize) {
-            if (pageSize < mPageSize) {
-                throw new IllegalArgumentException("Page size cannot be reduced");
-            }
-            if (mPages.size() != 1 || mTrailingNullCount != 0) {
-                // not in single, last page allocated case - can't change page size
-                throw new IllegalArgumentException(
-                        "Page size can change only if last page is only one present");
-            }
-            mPageSize = pageSize;
-        }
-
-        final int maxPageCount = (size() + mPageSize - 1) / mPageSize;
-        int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0);
-        int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1);
-
-        allocatePageRange(minimumPage, maximumPage);
-        int leadingNullPages = mLeadingNullCount / mPageSize;
-        for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
-            int localPageIndex = pageIndex - leadingNullPages;
-            if (mPages.get(localPageIndex) == null) {
-                //noinspection unchecked
-                mPages.set(localPageIndex, PLACEHOLDER_LIST);
-                callback.onPagePlaceholderInserted(pageIndex);
-            }
-        }
-    }
-
-    public boolean hasPage(int pageSize, int index) {
-        // NOTE: we pass pageSize here to avoid in case mPageSize
-        // not fully initialized (when last page only one loaded)
-        int leadingNullPages = mLeadingNullCount / pageSize;
-
-        if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) {
-            return false;
-        }
-
-        List<T> page = mPages.get(index - leadingNullPages);
-
-        return page != null && page != PLACEHOLDER_LIST;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount
-                + ", storage " + mStorageCount
-                + ", trailing " + getTrailingNullCount());
-
-        for (int i = 0; i < mPages.size(); i++) {
-            ret.append(" ").append(mPages.get(i));
-        }
-        return ret.toString();
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
deleted file mode 100644
index 1fe6725..0000000
--- a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * Copyright 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.paging;
-
-import android.arch.core.util.Function;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
- * arbitrary page positions.
- * <p>
- * Extend PositionalDataSource if you can load pages of a requested size at arbitrary
- * positions, and provide a fixed item count. If your data source can't support loading arbitrary
- * requested page sizes (e.g. when network page size constraints are only known at runtime), use
- * either {@link PageKeyedDataSource} or {@link ItemKeyedDataSource} instead.
- * <p>
- * Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled}
- * PositionalDataSource requires counting the size of the data set. This allows pages to be tiled in
- * at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}.
- * If placeholders are disabled, initialize with the two parameter
- * {@link LoadInitialCallback#onResult(List, int)}.
- * <p>
- * Room can generate a Factory of PositionalDataSources for you:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- *     {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
- *     public abstract DataSource.Factory&lt;Integer, User> loadUsersByAgeDesc();
- * }</pre>
- *
- * @param <T> Type of items being loaded by the PositionalDataSource.
- */
-public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
-
-    /**
-     * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static class LoadInitialParams {
-        /**
-         * Initial load position requested.
-         * <p>
-         * Note that this may not be within the bounds of your data set, it may need to be adjusted
-         * before you execute your load.
-         */
-        public final int requestedStartPosition;
-
-        /**
-         * Requested number of items to load.
-         * <p>
-         * Note that this may be larger than available data.
-         */
-        public final int requestedLoadSize;
-
-        /**
-         * Defines page size acceptable for return values.
-         * <p>
-         * List of items passed to the callback must be an integer multiple of page size.
-         */
-        public final int pageSize;
-
-        /**
-         * Defines whether placeholders are enabled, and whether the total count passed to
-         * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored.
-         */
-        public final boolean placeholdersEnabled;
-
-        public LoadInitialParams(
-                int requestedStartPosition,
-                int requestedLoadSize,
-                int pageSize,
-                boolean placeholdersEnabled) {
-            this.requestedStartPosition = requestedStartPosition;
-            this.requestedLoadSize = requestedLoadSize;
-            this.pageSize = pageSize;
-            this.placeholdersEnabled = placeholdersEnabled;
-        }
-    }
-
-    /**
-     * Holder object for inputs to {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static class LoadRangeParams {
-        /**
-         * Start position of data to load.
-         * <p>
-         * Returned data must start at this position.
-         */
-        public final int startPosition;
-        /**
-         * Number of items to load.
-         * <p>
-         * Returned data must be of this size, unless at end of the list.
-         */
-        public final int loadSize;
-
-        public LoadRangeParams(int startPosition, int loadSize) {
-            this.startPosition = startPosition;
-            this.loadSize = loadSize;
-        }
-    }
-
-    /**
-     * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
-     * to return data, position, and count.
-     * <p>
-     * A callback should be called only once, and may throw if called again.
-     * <p>
-     * It is always valid for a DataSource loading method that takes a callback to stash the
-     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
-     * temporary, recoverable error states (such as a network error that can be retried).
-     *
-     * @param <T> Type of items being loaded.
-     */
-    public abstract static class LoadInitialCallback<T> {
-        /**
-         * Called to pass initial load state from a DataSource.
-         * <p>
-         * Call this method from your DataSource's {@code loadInitial} function to return data,
-         * and inform how many placeholders should be shown before and after. If counting is cheap
-         * to compute (for example, if a network load returns the information regardless), it's
-         * recommended to pass the total size to the totalCount parameter. If placeholders are not
-         * requested (when {@link LoadInitialParams#placeholdersEnabled} is false), you can instead
-         * call {@link #onResult(List, int)}.
-         *
-         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
-         *             is treated as empty, and no further loads will occur.
-         * @param position Position of the item at the front of the list. If there are {@code N}
-         *                 items before the items in data that can be loaded from this DataSource,
-         *                 pass {@code N}.
-         * @param totalCount Total number of items that may be returned from this DataSource.
-         *                   Includes the number in the initial {@code data} parameter
-         *                   as well as any items that can be loaded in front or behind of
-         *                   {@code data}.
-         */
-        public abstract void onResult(@NonNull List<T> data, int position, int totalCount);
-
-        /**
-         * Called to pass initial load state from a DataSource without total count,
-         * when placeholders aren't requested.
-         * <p class="note"><strong>Note:</strong> This method can only be called when placeholders
-         * are disabled ({@link LoadInitialParams#placeholdersEnabled} is false).
-         * <p>
-         * Call this method from your DataSource's {@code loadInitial} function to return data,
-         * if position is known but total size is not. If placeholders are requested, call the three
-         * parameter variant: {@link #onResult(List, int, int)}.
-         *
-         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
-         *             is treated as empty, and no further loads will occur.
-         * @param position Position of the item at the front of the list. If there are {@code N}
-         *                 items before the items in data that can be provided by this DataSource,
-         *                 pass {@code N}.
-         */
-        public abstract void onResult(@NonNull List<T> data, int position);
-    }
-
-    /**
-     * Callback for PositionalDataSource {@link #loadRange(LoadRangeParams, LoadRangeCallback)}
-     * to return data.
-     * <p>
-     * A callback should be called only once, and may throw if called again.
-     * <p>
-     * It is always valid for a DataSource loading method that takes a callback to stash the
-     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
-     * temporary, recoverable error states (such as a network error that can be retried).
-     *
-     * @param <T> Type of items being loaded.
-     */
-    public abstract static class LoadRangeCallback<T> {
-        /**
-         * Called to pass loaded data from {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
-         *
-         * @param data List of items loaded from the DataSource. Must be same size as requested,
-         *             unless at end of list.
-         */
-        public abstract void onResult(@NonNull List<T> data);
-    }
-
-    static class LoadInitialCallbackImpl<T> extends LoadInitialCallback<T> {
-        final LoadCallbackHelper<T> mCallbackHelper;
-        private final boolean mCountingEnabled;
-        private final int mPageSize;
-
-        LoadInitialCallbackImpl(@NonNull PositionalDataSource dataSource, boolean countingEnabled,
-                int pageSize, PageResult.Receiver<T> receiver) {
-            mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver);
-            mCountingEnabled = countingEnabled;
-            mPageSize = pageSize;
-            if (mPageSize < 1) {
-                throw new IllegalArgumentException("Page size must be non-negative");
-            }
-        }
-
-        @Override
-        public void onResult(@NonNull List<T> data, int position, int totalCount) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
-                if (position + data.size() != totalCount
-                        && data.size() % mPageSize != 0) {
-                    throw new IllegalArgumentException("PositionalDataSource requires initial load"
-                            + " size to be a multiple of page size to support internal tiling."
-                            + " loadSize " + data.size() + ", position " + position
-                            + ", totalCount " + totalCount + ", pageSize " + mPageSize);
-                }
-
-                if (mCountingEnabled) {
-                    int trailingUnloadedCount = totalCount - position - data.size();
-                    mCallbackHelper.dispatchResultToReceiver(
-                            new PageResult<>(data, position, trailingUnloadedCount, 0));
-                } else {
-                    // Only occurs when wrapped as contiguous
-                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
-                }
-            }
-        }
-
-        @Override
-        public void onResult(@NonNull List<T> data, int position) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                if (position < 0) {
-                    throw new IllegalArgumentException("Position must be non-negative");
-                }
-                if (data.isEmpty() && position != 0) {
-                    throw new IllegalArgumentException(
-                            "Initial result cannot be empty if items are present in data set.");
-                }
-                if (mCountingEnabled) {
-                    throw new IllegalStateException("Placeholders requested, but totalCount not"
-                            + " provided. Please call the three-parameter onResult method, or"
-                            + " disable placeholders in the PagedList.Config");
-                }
-                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
-            }
-        }
-    }
-
-    static class LoadRangeCallbackImpl<T> extends LoadRangeCallback<T> {
-        private LoadCallbackHelper<T> mCallbackHelper;
-        private final int mPositionOffset;
-        LoadRangeCallbackImpl(@NonNull PositionalDataSource dataSource,
-                @PageResult.ResultType int resultType, int positionOffset,
-                Executor mainThreadExecutor, PageResult.Receiver<T> receiver) {
-            mCallbackHelper = new LoadCallbackHelper<>(
-                    dataSource, resultType, mainThreadExecutor, receiver);
-            mPositionOffset = positionOffset;
-        }
-
-        @Override
-        public void onResult(@NonNull List<T> data) {
-            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
-                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
-                        data, 0, 0, mPositionOffset));
-            }
-        }
-    }
-
-    final void dispatchLoadInitial(boolean acceptCount,
-            int requestedStartPosition, int requestedLoadSize, int pageSize,
-            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
-        LoadInitialCallbackImpl<T> callback =
-                new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
-
-        LoadInitialParams params = new LoadInitialParams(
-                requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
-        loadInitial(params, callback);
-
-        // If initialLoad's callback is not called within the body, we force any following calls
-        // to post to the UI thread. This constructor may be run on a background thread, but
-        // after constructor, mutation must happen on UI thread.
-        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
-    }
-
-    final void dispatchLoadRange(@PageResult.ResultType int resultType, int startPosition,
-            int count, @NonNull Executor mainThreadExecutor,
-            @NonNull PageResult.Receiver<T> receiver) {
-        LoadRangeCallback<T> callback = new LoadRangeCallbackImpl<>(
-                this, resultType, startPosition, mainThreadExecutor, receiver);
-        if (count == 0) {
-            callback.onResult(Collections.<T>emptyList());
-        } else {
-            loadRange(new LoadRangeParams(startPosition, count), callback);
-        }
-    }
-
-    /**
-     * Load initial list data.
-     * <p>
-     * This method is called to load the initial page(s) from the DataSource.
-     * <p>
-     * Result list must be a multiple of pageSize to enable efficient tiling.
-     *
-     * @param params Parameters for initial load, including requested start position, load size, and
-     *               page size.
-     * @param callback Callback that receives initial load data, including
-     *                 position and total data set size.
-     */
-    @WorkerThread
-    public abstract void loadInitial(
-            @NonNull LoadInitialParams params,
-            @NonNull LoadInitialCallback<T> callback);
-
-    /**
-     * Called to load a range of data from the DataSource.
-     * <p>
-     * This method is called to load additional pages from the DataSource after the
-     * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
-     * <p>
-     * Unlike {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}, this method must return
-     * the number of items requested, at the position requested.
-     *
-     * @param params Parameters for load, including start position and load size.
-     * @param callback Callback that receives loaded data.
-     */
-    @WorkerThread
-    public abstract void loadRange(@NonNull LoadRangeParams params,
-            @NonNull LoadRangeCallback<T> callback);
-
-    @Override
-    boolean isContiguous() {
-        return false;
-    }
-
-    @NonNull
-    ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
-        return new ContiguousWithoutPlaceholdersWrapper<>(this);
-    }
-
-    /**
-     * Helper for computing an initial position in
-     * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
-     * computed ahead of loading.
-     * <p>
-     * The value computed by this function will do bounds checking, page alignment, and positioning
-     * based on initial load size requested.
-     * <p>
-     * Example usage in a PositionalDataSource subclass:
-     * <pre>
-     * class ItemDataSource extends PositionalDataSource&lt;Item> {
-     *     private int computeCount() {
-     *         // actual count code here
-     *     }
-     *
-     *     private List&lt;Item> loadRangeInternal(int startPosition, int loadCount) {
-     *         // actual load code here
-     *     }
-     *
-     *     {@literal @}Override
-     *     public void loadInitial({@literal @}NonNull LoadInitialParams params,
-     *             {@literal @}NonNull LoadInitialCallback&lt;Item> callback) {
-     *         int totalCount = computeCount();
-     *         int position = computeInitialLoadPosition(params, totalCount);
-     *         int loadSize = computeInitialLoadSize(params, position, totalCount);
-     *         callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
-     *     }
-     *
-     *     {@literal @}Override
-     *     public void loadRange({@literal @}NonNull LoadRangeParams params,
-     *             {@literal @}NonNull LoadRangeCallback&lt;Item> callback) {
-     *         callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
-     *     }
-     * }</pre>
-     *
-     * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
-     *               including page size, and requested start/loadSize.
-     * @param totalCount Total size of the data set.
-     * @return Position to start loading at.
-     *
-     * @see #computeInitialLoadSize(LoadInitialParams, int, int)
-     */
-    public static int computeInitialLoadPosition(@NonNull LoadInitialParams params,
-            int totalCount) {
-        int position = params.requestedStartPosition;
-        int initialLoadSize = params.requestedLoadSize;
-        int pageSize = params.pageSize;
-
-        int roundedPageStart = Math.round(position / pageSize) * pageSize;
-
-        // maximum start pos is that which will encompass end of list
-        int maximumLoadPage = ((totalCount - initialLoadSize + pageSize - 1) / pageSize) * pageSize;
-        roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
-
-        // minimum start position is 0
-        roundedPageStart = Math.max(0, roundedPageStart);
-
-        return roundedPageStart;
-    }
-
-    /**
-     * Helper for computing an initial load size in
-     * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
-     * computed ahead of loading.
-     * <p>
-     * This function takes the requested load size, and bounds checks it against the value returned
-     * by {@link #computeInitialLoadPosition(LoadInitialParams, int)}.
-     * <p>
-     * Example usage in a PositionalDataSource subclass:
-     * <pre>
-     * class ItemDataSource extends PositionalDataSource&lt;Item> {
-     *     private int computeCount() {
-     *         // actual count code here
-     *     }
-     *
-     *     private List&lt;Item> loadRangeInternal(int startPosition, int loadCount) {
-     *         // actual load code here
-     *     }
-     *
-     *     {@literal @}Override
-     *     public void loadInitial({@literal @}NonNull LoadInitialParams params,
-     *             {@literal @}NonNull LoadInitialCallback&lt;Item> callback) {
-     *         int totalCount = computeCount();
-     *         int position = computeInitialLoadPosition(params, totalCount);
-     *         int loadSize = computeInitialLoadSize(params, position, totalCount);
-     *         callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
-     *     }
-     *
-     *     {@literal @}Override
-     *     public void loadRange({@literal @}NonNull LoadRangeParams params,
-     *             {@literal @}NonNull LoadRangeCallback&lt;Item> callback) {
-     *         callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
-     *     }
-     * }</pre>
-     *
-     * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
-     *               including page size, and requested start/loadSize.
-     * @param initialLoadPosition Value returned by
-     *                          {@link #computeInitialLoadPosition(LoadInitialParams, int)}
-     * @param totalCount Total size of the data set.
-     * @return Number of items to load.
-     *
-     * @see #computeInitialLoadPosition(LoadInitialParams, int)
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static int computeInitialLoadSize(@NonNull LoadInitialParams params,
-            int initialLoadPosition, int totalCount) {
-        return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize);
-    }
-
-    @SuppressWarnings("deprecation")
-    static class ContiguousWithoutPlaceholdersWrapper<Value>
-            extends ContiguousDataSource<Integer, Value> {
-
-        @NonNull
-        final PositionalDataSource<Value> mPositionalDataSource;
-
-        ContiguousWithoutPlaceholdersWrapper(
-                @NonNull PositionalDataSource<Value> positionalDataSource) {
-            mPositionalDataSource = positionalDataSource;
-        }
-
-        @NonNull
-        @Override
-        public <ToValue> DataSource<Integer, ToValue> mapByPage(
-                @NonNull Function<List<Value>, List<ToValue>> function) {
-            throw new UnsupportedOperationException(
-                    "Inaccessible inner type doesn't support map op");
-        }
-
-        @NonNull
-        @Override
-        public <ToValue> DataSource<Integer, ToValue> map(
-                @NonNull Function<Value, ToValue> function) {
-            throw new UnsupportedOperationException(
-                    "Inaccessible inner type doesn't support map op");
-        }
-
-        @Override
-        void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
-                boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
-                @NonNull PageResult.Receiver<Value> receiver) {
-            final int convertPosition = position == null ? 0 : position;
-
-            // Note enablePlaceholders will be false here, but we don't have a way to communicate
-            // this to PositionalDataSource. This is fine, because only the list and its position
-            // offset will be consumed by the LoadInitialCallback.
-            mPositionalDataSource.dispatchLoadInitial(false, convertPosition, initialLoadSize,
-                    pageSize, mainThreadExecutor, receiver);
-        }
-
-        @Override
-        void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
-                @NonNull Executor mainThreadExecutor,
-                @NonNull PageResult.Receiver<Value> receiver) {
-            int startIndex = currentEndIndex + 1;
-            mPositionalDataSource.dispatchLoadRange(
-                    PageResult.APPEND, startIndex, pageSize, mainThreadExecutor, receiver);
-        }
-
-        @Override
-        void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
-                int pageSize, @NonNull Executor mainThreadExecutor,
-                @NonNull PageResult.Receiver<Value> receiver) {
-
-            int startIndex = currentBeginIndex - 1;
-            if (startIndex < 0) {
-                // trigger empty list load
-                mPositionalDataSource.dispatchLoadRange(
-                        PageResult.PREPEND, startIndex, 0, mainThreadExecutor, receiver);
-            } else {
-                int loadSize = Math.min(pageSize, startIndex + 1);
-                startIndex = startIndex - loadSize + 1;
-                mPositionalDataSource.dispatchLoadRange(
-                        PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver);
-            }
-        }
-
-        @Override
-        Integer getKey(int position, Value item) {
-            return position;
-        }
-
-    }
-
-    @NonNull
-    @Override
-    public final <V> PositionalDataSource<V> mapByPage(
-            @NonNull Function<List<T>, List<V>> function) {
-        return new WrapperPositionalDataSource<>(this, function);
-    }
-
-    @NonNull
-    @Override
-    public final <V> PositionalDataSource<V> map(@NonNull Function<T, V> function) {
-        return mapByPage(createListFunction(function));
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/SnapshotPagedList.java b/paging/common/src/main/java/android/arch/paging/SnapshotPagedList.java
deleted file mode 100644
index 4627d2a..0000000
--- a/paging/common/src/main/java/android/arch/paging/SnapshotPagedList.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-class SnapshotPagedList<T> extends PagedList<T> {
-    private final boolean mContiguous;
-    private final Object mLastKey;
-    private final DataSource<?, T> mDataSource;
-
-    SnapshotPagedList(@NonNull PagedList<T> pagedList) {
-        super(pagedList.mStorage.snapshot(),
-                pagedList.mMainThreadExecutor,
-                pagedList.mBackgroundThreadExecutor,
-                null,
-                pagedList.mConfig);
-        mDataSource = pagedList.getDataSource();
-        mContiguous = pagedList.isContiguous();
-        mLastKey = pagedList.getLastKey();
-    }
-
-    @Override
-    public boolean isImmutable() {
-        return true;
-    }
-
-    @Override
-    public boolean isDetached() {
-        return true;
-    }
-
-    @Override
-    boolean isContiguous() {
-        return mContiguous;
-    }
-
-    @Nullable
-    @Override
-    public Object getLastKey() {
-        return mLastKey;
-    }
-
-    @NonNull
-    @Override
-    public DataSource<?, T> getDataSource() {
-        return mDataSource;
-    }
-
-    @Override
-    void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot,
-            @NonNull Callback callback) {
-    }
-
-    @Override
-    void loadAroundInternal(int index) {
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java b/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
deleted file mode 100644
index 7285aa4..0000000
--- a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-
-import java.util.Collections;
-import java.util.List;
-
-// NOTE: Room 1.0 depends on this class, so it should not be removed until
-// we can require a version of Room that uses PositionalDataSource directly
-/**
- * @param <T> Type loaded by the TiledDataSource.
- *
- * @deprecated Use {@link PositionalDataSource}
- * @hide
- */
-@SuppressWarnings("DeprecatedIsStillUsed")
-@Deprecated
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class TiledDataSource<T> extends PositionalDataSource<T> {
-
-    @WorkerThread
-    public abstract int countItems();
-
-    @Override
-    boolean isContiguous() {
-        return false;
-    }
-
-    @WorkerThread
-    public abstract List<T> loadRange(int startPosition, int count);
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams params,
-            @NonNull LoadInitialCallback<T> callback) {
-        int totalCount = countItems();
-        if (totalCount == 0) {
-            callback.onResult(Collections.<T>emptyList(), 0, 0);
-            return;
-        }
-
-        // bound the size requested, based on known count
-        final int firstLoadPosition = computeInitialLoadPosition(params, totalCount);
-        final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);
-
-        // convert from legacy behavior
-        List<T> list = loadRange(firstLoadPosition, firstLoadSize);
-        if (list != null && list.size() == firstLoadSize) {
-            callback.onResult(list, firstLoadPosition, totalCount);
-        } else {
-            // null list, or size doesn't match request
-            // The size check is a WAR for Room 1.0, subsequent versions do the check in Room
-            invalidate();
-        }
-    }
-
-    @Override
-    public void loadRange(@NonNull LoadRangeParams params,
-            @NonNull LoadRangeCallback<T> callback) {
-        List<T> list = loadRange(params.startPosition, params.loadSize);
-        if (list != null) {
-            callback.onResult(list);
-        } else {
-            invalidate();
-        }
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java b/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
deleted file mode 100644
index 374fe2a..0000000
--- a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.AnyThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-
-import java.util.concurrent.Executor;
-
-class TiledPagedList<T> extends PagedList<T>
-        implements PagedStorage.Callback {
-    private final PositionalDataSource<T> mDataSource;
-
-    private PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() {
-        // Creation thread for initial synchronous load, otherwise main thread
-        // Safe to access main thread only state - no other thread has reference during construction
-        @AnyThread
-        @Override
-        public void onPageResult(@PageResult.ResultType int type,
-                @NonNull PageResult<T> pageResult) {
-            if (pageResult.isInvalid()) {
-                detach();
-                return;
-            }
-
-            if (isDetached()) {
-                // No op, have detached
-                return;
-            }
-
-            if (type != PageResult.INIT && type != PageResult.TILE) {
-                throw new IllegalArgumentException("unexpected resultType" + type);
-            }
-
-            if (mStorage.getPageCount() == 0) {
-                mStorage.initAndSplit(
-                        pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
-                        pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this);
-            } else {
-                mStorage.insertPage(pageResult.positionOffset, pageResult.page,
-                        TiledPagedList.this);
-            }
-
-            if (mBoundaryCallback != null) {
-                boolean deferEmpty = mStorage.size() == 0;
-                boolean deferBegin = !deferEmpty
-                        && pageResult.leadingNulls == 0
-                        && pageResult.positionOffset == 0;
-                int size = size();
-                boolean deferEnd = !deferEmpty
-                        && ((type == PageResult.INIT && pageResult.trailingNulls == 0)
-                                || (type == PageResult.TILE
-                                        && pageResult.positionOffset
-                                                == (size - size % mConfig.pageSize)));
-                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
-            }
-        }
-    };
-
-    @WorkerThread
-    TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
-            @NonNull Executor mainThreadExecutor,
-            @NonNull Executor backgroundThreadExecutor,
-            @Nullable BoundaryCallback<T> boundaryCallback,
-            @NonNull Config config,
-            int position) {
-        super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
-                boundaryCallback, config);
-        mDataSource = dataSource;
-
-        final int pageSize = mConfig.pageSize;
-        mLastLoad = position;
-
-        if (mDataSource.isInvalid()) {
-            detach();
-        } else {
-            final int firstLoadSize =
-                    (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize;
-
-            final int idealStart = position - firstLoadSize / 2;
-            final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
-
-            mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize,
-                    pageSize, mMainThreadExecutor, mReceiver);
-        }
-    }
-
-    @Override
-    boolean isContiguous() {
-        return false;
-    }
-
-    @NonNull
-    @Override
-    public DataSource<?, T> getDataSource() {
-        return mDataSource;
-    }
-
-    @Nullable
-    @Override
-    public Object getLastKey() {
-        return mLastLoad;
-    }
-
-    @Override
-    protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
-            @NonNull Callback callback) {
-        //noinspection UnnecessaryLocalVariable
-        final PagedStorage<T> snapshot = pagedListSnapshot.mStorage;
-
-        if (snapshot.isEmpty()
-                || mStorage.size() != snapshot.size()) {
-            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
-                    + " to be a snapshot of this PagedList");
-        }
-
-        // loop through each page and signal the callback for any pages that are present now,
-        // but not in the snapshot.
-        final int pageSize = mConfig.pageSize;
-        final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
-        final int pageCount = mStorage.getPageCount();
-        for (int i = 0; i < pageCount; i++) {
-            int pageIndex = i + leadingNullPages;
-            int updatedPages = 0;
-            // count number of consecutive pages that were added since the snapshot...
-            while (updatedPages < mStorage.getPageCount()
-                    && mStorage.hasPage(pageSize, pageIndex + updatedPages)
-                    && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
-                updatedPages++;
-            }
-            // and signal them all at once to the callback
-            if (updatedPages > 0) {
-                callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
-                i += updatedPages - 1;
-            }
-        }
-    }
-
-    @Override
-    protected void loadAroundInternal(int index) {
-        mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this);
-    }
-
-    @Override
-    public void onInitialized(int count) {
-        notifyInserted(0, count);
-    }
-
-    @Override
-    public void onPagePrepended(int leadingNulls, int changed, int added) {
-        throw new IllegalStateException("Contiguous callback on TiledPagedList");
-    }
-
-    @Override
-    public void onPageAppended(int endPosition, int changed, int added) {
-        throw new IllegalStateException("Contiguous callback on TiledPagedList");
-    }
-
-    @Override
-    public void onPagePlaceholderInserted(final int pageIndex) {
-        // placeholder means initialize a load
-        mBackgroundThreadExecutor.execute(new Runnable() {
-            @Override
-            public void run() {
-                if (isDetached()) {
-                    return;
-                }
-                final int pageSize = mConfig.pageSize;
-
-                if (mDataSource.isInvalid()) {
-                    detach();
-                } else {
-                    int startPosition = pageIndex * pageSize;
-                    int count = Math.min(pageSize, mStorage.size() - startPosition);
-                    mDataSource.dispatchLoadRange(
-                            PageResult.TILE, startPosition, count, mMainThreadExecutor, mReceiver);
-                }
-            }
-        });
-    }
-
-    @Override
-    public void onPageInserted(int start, int count) {
-        notifyChanged(start, count);
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/WrapperItemKeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/WrapperItemKeyedDataSource.java
deleted file mode 100644
index 93520ca..0000000
--- a/paging/common/src/main/java/android/arch/paging/WrapperItemKeyedDataSource.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2018 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.paging;
-
-import android.arch.core.util.Function;
-import android.support.annotation.NonNull;
-
-import java.util.IdentityHashMap;
-import java.util.List;
-
-class WrapperItemKeyedDataSource<K, A, B> extends ItemKeyedDataSource<K, B> {
-    private final ItemKeyedDataSource<K, A> mSource;
-    private final Function<List<A>, List<B>> mListFunction;
-    private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
-        @Override
-        public void onInvalidated() {
-            invalidate();
-            removeCallback();
-        }
-    };
-
-    private final IdentityHashMap<B, K> mKeyMap = new IdentityHashMap<>();
-
-    WrapperItemKeyedDataSource(ItemKeyedDataSource<K, A> source,
-            Function<List<A>, List<B>> listFunction) {
-        mSource = source;
-        mListFunction = listFunction;
-        mSource.addInvalidatedCallback(mInvalidatedCallback);
-    }
-
-    private void removeCallback() {
-        mSource.removeInvalidatedCallback(mInvalidatedCallback);
-    }
-
-    private List<B> convertWithStashedKeys(List<A> source) {
-        List<B> dest = convert(mListFunction, source);
-        synchronized (mKeyMap) {
-            // synchronize on mKeyMap, since multiple loads may occur simultaneously.
-            // Note: manually sync avoids locking per-item (e.g. Collections.synchronizedMap)
-            for (int i = 0; i < dest.size(); i++) {
-                mKeyMap.put(dest.get(i), mSource.getKey(source.get(i)));
-            }
-        }
-        return dest;
-    }
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams<K> params,
-            final @NonNull LoadInitialCallback<B> callback) {
-        mSource.loadInitial(params, new LoadInitialCallback<A>() {
-            @Override
-            public void onResult(@NonNull List<A> data, int position, int totalCount) {
-                callback.onResult(convertWithStashedKeys(data), position, totalCount);
-            }
-
-            @Override
-            public void onResult(@NonNull List<A> data) {
-                callback.onResult(convertWithStashedKeys(data));
-            }
-        });
-    }
-
-    @Override
-    public void loadAfter(@NonNull LoadParams<K> params,
-            final @NonNull LoadCallback<B> callback) {
-        mSource.loadAfter(params, new LoadCallback<A>() {
-            @Override
-            public void onResult(@NonNull List<A> data) {
-                callback.onResult(convertWithStashedKeys(data));
-            }
-        });
-    }
-
-    @Override
-    public void loadBefore(@NonNull LoadParams<K> params,
-            final @NonNull LoadCallback<B> callback) {
-        mSource.loadBefore(params, new LoadCallback<A>() {
-            @Override
-            public void onResult(@NonNull List<A> data) {
-                callback.onResult(convertWithStashedKeys(data));
-            }
-        });
-    }
-
-    @NonNull
-    @Override
-    public K getKey(@NonNull B item) {
-        synchronized (mKeyMap) {
-            return mKeyMap.get(item);
-        }
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/WrapperPageKeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/WrapperPageKeyedDataSource.java
deleted file mode 100644
index e6fa274..0000000
--- a/paging/common/src/main/java/android/arch/paging/WrapperPageKeyedDataSource.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2018 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.paging;
-
-import android.arch.core.util.Function;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.List;
-
-class WrapperPageKeyedDataSource<K, A, B> extends PageKeyedDataSource<K, B> {
-    private final PageKeyedDataSource<K, A> mSource;
-    private final Function<List<A>, List<B>> mListFunction;
-    private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
-        @Override
-        public void onInvalidated() {
-            invalidate();
-            removeCallback();
-        }
-    };
-
-    WrapperPageKeyedDataSource(PageKeyedDataSource<K, A> source,
-            Function<List<A>, List<B>> listFunction) {
-        mSource = source;
-        mListFunction = listFunction;
-        mSource.addInvalidatedCallback(mInvalidatedCallback);
-    }
-
-    private void removeCallback() {
-        mSource.removeInvalidatedCallback(mInvalidatedCallback);
-    }
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams<K> params,
-            final @NonNull LoadInitialCallback<K, B> callback) {
-        mSource.loadInitial(params, new LoadInitialCallback<K, A>() {
-            @Override
-            public void onResult(@NonNull List<A> data, int position, int totalCount,
-                    @Nullable K previousPageKey, @Nullable K nextPageKey) {
-                callback.onResult(convert(mListFunction, data), position, totalCount,
-                        previousPageKey, nextPageKey);
-            }
-
-            @Override
-            public void onResult(@NonNull List<A> data, @Nullable K previousPageKey,
-                    @Nullable K nextPageKey) {
-                callback.onResult(convert(mListFunction, data), previousPageKey, nextPageKey);
-            }
-        });
-    }
-
-    @Override
-    public void loadBefore(@NonNull LoadParams<K> params,
-            final @NonNull LoadCallback<K, B> callback) {
-        mSource.loadBefore(params, new LoadCallback<K, A>() {
-            @Override
-            public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
-                callback.onResult(convert(mListFunction, data), adjacentPageKey);
-            }
-        });
-    }
-
-    @Override
-    public void loadAfter(@NonNull LoadParams<K> params,
-            final @NonNull LoadCallback<K, B> callback) {
-        mSource.loadAfter(params, new LoadCallback<K, A>() {
-            @Override
-            public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
-                callback.onResult(convert(mListFunction, data), adjacentPageKey);
-            }
-        });
-    }
-}
diff --git a/paging/common/src/main/java/android/arch/paging/WrapperPositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/WrapperPositionalDataSource.java
deleted file mode 100644
index 0626f87..0000000
--- a/paging/common/src/main/java/android/arch/paging/WrapperPositionalDataSource.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2018 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.paging;
-
-import android.arch.core.util.Function;
-import android.support.annotation.NonNull;
-
-import java.util.List;
-
-class WrapperPositionalDataSource<A, B> extends PositionalDataSource<B> {
-    private final PositionalDataSource<A> mSource;
-    private final Function<List<A>, List<B>> mListFunction;
-
-    private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
-        @Override
-        public void onInvalidated() {
-            invalidate();
-            removeCallback();
-        }
-    };
-
-    WrapperPositionalDataSource(PositionalDataSource<A> source,
-            Function<List<A>, List<B>> listFunction) {
-        mSource = source;
-        mListFunction = listFunction;
-        mSource.addInvalidatedCallback(mInvalidatedCallback);
-    }
-
-    private void removeCallback() {
-        mSource.removeInvalidatedCallback(mInvalidatedCallback);
-    }
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams params,
-            final @NonNull LoadInitialCallback<B> callback) {
-        mSource.loadInitial(params, new LoadInitialCallback<A>() {
-            @Override
-            public void onResult(@NonNull List<A> data, int position, int totalCount) {
-                callback.onResult(convert(mListFunction, data), position, totalCount);
-            }
-
-            @Override
-            public void onResult(@NonNull List<A> data, int position) {
-                callback.onResult(convert(mListFunction, data), position);
-            }
-        });
-    }
-
-    @Override
-    public void loadRange(@NonNull LoadRangeParams params,
-            final @NonNull LoadRangeCallback<B> callback) {
-        mSource.loadRange(params, new LoadRangeCallback<A>() {
-            @Override
-            public void onResult(@NonNull List<A> data) {
-                callback.onResult(convert(mListFunction, data));
-            }
-        });
-    }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ContiguousDataSource.java b/paging/common/src/main/java/androidx/paging/ContiguousDataSource.java
new file mode 100644
index 0000000..dc95d66
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/ContiguousDataSource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
+    @Override
+    boolean isContiguous() {
+        return true;
+    }
+
+    abstract void dispatchLoadInitial(
+            @Nullable Key key,
+            int initialLoadSize,
+            int pageSize,
+            boolean enablePlaceholders,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver);
+
+    abstract void dispatchLoadAfter(
+            int currentEndIndex,
+            @NonNull Value currentEndItem,
+            int pageSize,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver);
+
+    abstract void dispatchLoadBefore(
+            int currentBeginIndex,
+            @NonNull Value currentBeginItem,
+            int pageSize,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver);
+
+    /**
+     * Get the key from either the position, or item, or null if position/item invalid.
+     * <p>
+     * Position may not match passed item's position - if trying to query the key from a position
+     * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
+     */
+    abstract Key getKey(int position, Value item);
+}
diff --git a/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java b/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
new file mode 100644
index 0000000..1c39a8b
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
+    private final ContiguousDataSource<K, V> mDataSource;
+    private boolean mPrependWorkerRunning = false;
+    private boolean mAppendWorkerRunning = false;
+
+    private int mPrependItemsRequested = 0;
+    private int mAppendItemsRequested = 0;
+
+    private PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
+        // Creation thread for initial synchronous load, otherwise main thread
+        // Safe to access main thread only state - no other thread has reference during construction
+        @AnyThread
+        @Override
+        public void onPageResult(@PageResult.ResultType int resultType,
+                @NonNull PageResult<V> pageResult) {
+            if (pageResult.isInvalid()) {
+                detach();
+                return;
+            }
+
+            if (isDetached()) {
+                // No op, have detached
+                return;
+            }
+
+            List<V> page = pageResult.page;
+            if (resultType == PageResult.INIT) {
+                mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
+                        pageResult.positionOffset, ContiguousPagedList.this);
+                if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
+                    // Because the ContiguousPagedList wasn't initialized with a last load position,
+                    // initialize it to the middle of the initial load
+                    mLastLoad =
+                            pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
+                }
+            } else if (resultType == PageResult.APPEND) {
+                mStorage.appendPage(page, ContiguousPagedList.this);
+            } else if (resultType == PageResult.PREPEND) {
+                mStorage.prependPage(page, ContiguousPagedList.this);
+            } else {
+                throw new IllegalArgumentException("unexpected resultType " + resultType);
+            }
+
+
+            if (mBoundaryCallback != null) {
+                boolean deferEmpty = mStorage.size() == 0;
+                boolean deferBegin = !deferEmpty
+                        && resultType == PageResult.PREPEND
+                        && pageResult.page.size() == 0;
+                boolean deferEnd = !deferEmpty
+                        && resultType == PageResult.APPEND
+                        && pageResult.page.size() == 0;
+                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
+            }
+        }
+    };
+
+    static final int LAST_LOAD_UNSPECIFIED = -1;
+
+    ContiguousPagedList(
+            @NonNull ContiguousDataSource<K, V> dataSource,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<V> boundaryCallback,
+            @NonNull Config config,
+            final @Nullable K key,
+            int lastLoad) {
+        super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
+                boundaryCallback, config);
+        mDataSource = dataSource;
+        mLastLoad = lastLoad;
+
+        if (mDataSource.isInvalid()) {
+            detach();
+        } else {
+            mDataSource.dispatchLoadInitial(key,
+                    mConfig.initialLoadSizeHint,
+                    mConfig.pageSize,
+                    mConfig.enablePlaceholders,
+                    mMainThreadExecutor,
+                    mReceiver);
+        }
+    }
+
+    @MainThread
+    @Override
+    void dispatchUpdatesSinceSnapshot(
+            @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
+        final PagedStorage<V> snapshot = pagedListSnapshot.mStorage;
+
+        final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
+        final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
+
+        final int previousTrailing = snapshot.getTrailingNullCount();
+        final int previousLeading = snapshot.getLeadingNullCount();
+
+        // Validate that the snapshot looks like a previous version of this list - if it's not,
+        // we can't be sure we'll dispatch callbacks safely
+        if (snapshot.isEmpty()
+                || newlyAppended < 0
+                || newlyPrepended < 0
+                || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
+                || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
+                || (mStorage.getStorageCount()
+                        != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
+            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+                    + " to be a snapshot of this PagedList");
+        }
+
+        if (newlyAppended != 0) {
+            final int changedCount = Math.min(previousTrailing, newlyAppended);
+            final int addedCount = newlyAppended - changedCount;
+
+            final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
+            if (changedCount != 0) {
+                callback.onChanged(endPosition, changedCount);
+            }
+            if (addedCount != 0) {
+                callback.onInserted(endPosition + changedCount, addedCount);
+            }
+        }
+        if (newlyPrepended != 0) {
+            final int changedCount = Math.min(previousLeading, newlyPrepended);
+            final int addedCount = newlyPrepended - changedCount;
+
+            if (changedCount != 0) {
+                callback.onChanged(previousLeading, changedCount);
+            }
+            if (addedCount != 0) {
+                callback.onInserted(0, addedCount);
+            }
+        }
+    }
+
+    @MainThread
+    @Override
+    protected void loadAroundInternal(int index) {
+        int prependItems = mConfig.prefetchDistance - (index - mStorage.getLeadingNullCount());
+        int appendItems = index + mConfig.prefetchDistance
+                - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
+
+        mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
+        if (mPrependItemsRequested > 0) {
+            schedulePrepend();
+        }
+
+        mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
+        if (mAppendItemsRequested > 0) {
+            scheduleAppend();
+        }
+    }
+
+    @MainThread
+    private void schedulePrepend() {
+        if (mPrependWorkerRunning) {
+            return;
+        }
+        mPrependWorkerRunning = true;
+
+        final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
+
+        // safe to access first item here - mStorage can't be empty if we're prepending
+        final V item = mStorage.getFirstLoadedItem();
+        mBackgroundThreadExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                if (isDetached()) {
+                    return;
+                }
+                if (mDataSource.isInvalid()) {
+                    detach();
+                } else {
+                    mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize,
+                            mMainThreadExecutor, mReceiver);
+                }
+
+            }
+        });
+    }
+
+    @MainThread
+    private void scheduleAppend() {
+        if (mAppendWorkerRunning) {
+            return;
+        }
+        mAppendWorkerRunning = true;
+
+        final int position = mStorage.getLeadingNullCount()
+                + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
+
+        // safe to access first item here - mStorage can't be empty if we're appending
+        final V item = mStorage.getLastLoadedItem();
+        mBackgroundThreadExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                if (isDetached()) {
+                    return;
+                }
+                if (mDataSource.isInvalid()) {
+                    detach();
+                } else {
+                    mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
+                            mMainThreadExecutor, mReceiver);
+                }
+            }
+        });
+    }
+
+    @Override
+    boolean isContiguous() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public DataSource<?, V> getDataSource() {
+        return mDataSource;
+    }
+
+    @Nullable
+    @Override
+    public Object getLastKey() {
+        return mDataSource.getKey(mLastLoad, mLastItem);
+    }
+
+    @MainThread
+    @Override
+    public void onInitialized(int count) {
+        notifyInserted(0, count);
+    }
+
+    @MainThread
+    @Override
+    public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
+        // consider whether to post more work, now that a page is fully prepended
+        mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
+        mPrependWorkerRunning = false;
+        if (mPrependItemsRequested > 0) {
+            // not done prepending, keep going
+            schedulePrepend();
+        }
+
+        // finally dispatch callbacks, after prepend may have already been scheduled
+        notifyChanged(leadingNulls, changedCount);
+        notifyInserted(0, addedCount);
+
+        offsetBoundaryAccessIndices(addedCount);
+    }
+
+    @MainThread
+    @Override
+    public void onPageAppended(int endPosition, int changedCount, int addedCount) {
+        // consider whether to post more work, now that a page is fully appended
+
+        mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
+        mAppendWorkerRunning = false;
+        if (mAppendItemsRequested > 0) {
+            // not done appending, keep going
+            scheduleAppend();
+        }
+
+        // finally dispatch callbacks, after append may have already been scheduled
+        notifyChanged(endPosition, changedCount);
+        notifyInserted(endPosition + changedCount, addedCount);
+    }
+
+    @MainThread
+    @Override
+    public void onPagePlaceholderInserted(int pageIndex) {
+        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
+    }
+
+    @MainThread
+    @Override
+    public void onPageInserted(int start, int count) {
+        throw new IllegalStateException("Tiled callback on ContiguousPagedList");
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/DataSource.java b/paging/common/src/main/java/androidx/paging/DataSource.java
new file mode 100644
index 0000000..55cd0a9
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/DataSource.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.arch.core.util.Function;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Base class for loading pages of snapshot data into a {@link PagedList}.
+ * <p>
+ * DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as
+ * it loads more data, but the data loaded cannot be updated. If the underlying data set is
+ * modified, a new PagedList / DataSource pair must be created to represent the new data.
+ * <h4>Loading Pages</h4>
+ * PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter}
+ * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
+ * <p>
+ * To control how and when a PagedList queries data from its DataSource, see
+ * {@link PagedList.Config}. The Config object defines things like load sizes and prefetch distance.
+ * <h4>Updating Paged Data</h4>
+ * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
+ * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
+ * content update occurs. A DataSource must detect that it cannot continue loading its
+ * snapshot (for instance, when Database query notices a table being invalidated), and call
+ * {@link #invalidate()}. Then a new PagedList / DataSource pair would be created to load data from
+ * the new state of the Database query.
+ * <p>
+ * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
+ * PagedList. For example, loading from network when the network's paging API doesn't provide
+ * updates.
+ * <p>
+ * To page in data from a source that does provide updates, you can create a
+ * {@link DataSource.Factory}, where each DataSource created is invalidated when an update to the
+ * data set occurs that makes the current snapshot invalid. For example, when paging a query from
+ * the Database, and the table being queried inserts or removes items. You can also use a
+ * DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content
+ * (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data,
+ * you can connect an explicit refresh signal to call {@link #invalidate()} on the current
+ * DataSource.
+ * <p>
+ * If you have more granular update signals, such as a network API signaling an update to a single
+ * item in the list, it's recommended to load data from network into memory. Then present that
+ * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
+ * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
+ * snapshot can be created.
+ * <h4>Implementing a DataSource</h4>
+ * To implement, extend one of the subclasses: {@link PageKeyedDataSource},
+ * {@link ItemKeyedDataSource}, or {@link PositionalDataSource}.
+ * <p>
+ * Use {@link PageKeyedDataSource} if pages you load embed keys for loading adjacent pages. For
+ * example a network response that returns some items, and a next/previous page links.
+ * <p>
+ * Use {@link ItemKeyedDataSource} if you need to use data from item {@code N-1} to load item
+ * {@code N}. For example, if requesting the backend for the next comments in the list
+ * requires the ID or timestamp of the most recent loaded comment, or if querying the next users
+ * from a name-sorted database query requires the name and unique ID of the previous.
+ * <p>
+ * Use {@link PositionalDataSource} if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. PositionalDataSource supports querying pages at
+ * arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that
+ * PositionalDataSource is required to respect page size for efficient tiling. If you want to
+ * override page size (e.g. when network page size constraints are only known at runtime), use one
+ * of the other DataSource classes.
+ * <p>
+ * Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not
+ * return {@code null} items in lists that it loads. This is so that users of the PagedList
+ * can differentiate unloaded placeholder items from content that has been paged in.
+ *
+ * @param <Key> Input used to trigger initial load from the DataSource. Often an Integer position.
+ * @param <Value> Value type loaded by the DataSource.
+ */
+@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
+public abstract class DataSource<Key, Value> {
+    /**
+     * Factory for DataSources.
+     * <p>
+     * Data-loading systems of an application or library can implement this interface to allow
+     * {@code LiveData<PagedList>}s to be created. For example, Room can provide a
+     * DataSource.Factory for a given SQL query:
+     *
+     * <pre>
+     * {@literal @}Dao
+     * interface UserDao {
+     *    {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+     *    public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
+     * }
+     * </pre>
+     * In the above sample, {@code Integer} is used because it is the {@code Key} type of
+     * PositionalDataSource. Currently, Room uses the {@code LIMIT}/{@code OFFSET} SQL keywords to
+     * page a large query with a PositionalDataSource.
+     *
+     * @param <Key> Key identifying items in DataSource.
+     * @param <Value> Type of items in the list loaded by the DataSources.
+     */
+    public abstract static class Factory<Key, Value> {
+        /**
+         * Create a DataSource.
+         * <p>
+         * The DataSource should invalidate itself if the snapshot is no longer valid. If a
+         * DataSource becomes invalid, the only way to query more data is to create a new DataSource
+         * from the Factory.
+         * <p>
+         * {@link LivePagedListBuilder} for example will construct a new PagedList and DataSource
+         * when the current DataSource is invalidated, and pass the new PagedList through the
+         * {@code LiveData<PagedList>} to observers.
+         *
+         * @return the new DataSource.
+         */
+        public abstract DataSource<Key, Value> create();
+
+        /**
+         * Applies the given function to each value emitted by DataSources produced by this Factory.
+         * <p>
+         * Same as {@link #mapByPage(Function)}, but operates on individual items.
+         *
+         * @param function Function that runs on each loaded item, returning items of a potentially
+         *                  new type.
+         * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
+         *
+         * @return A new DataSource.Factory, which transforms items using the given function.
+         *
+         * @see #mapByPage(Function)
+         * @see DataSource#map(Function)
+         * @see DataSource#mapByPage(Function)
+         */
+        @NonNull
+        public <ToValue> DataSource.Factory<Key, ToValue> map(
+                @NonNull Function<Value, ToValue> function) {
+            return mapByPage(createListFunction(function));
+        }
+
+        /**
+         * Applies the given function to each value emitted by DataSources produced by this Factory.
+         * <p>
+         * Same as {@link #map(Function)}, but allows for batch conversions.
+         *
+         * @param function Function that runs on each loaded page, returning items of a potentially
+         *                  new type.
+         * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
+         *
+         * @return A new DataSource.Factory, which transforms items using the given function.
+         *
+         * @see #map(Function)
+         * @see DataSource#map(Function)
+         * @see DataSource#mapByPage(Function)
+         */
+        @NonNull
+        public <ToValue> DataSource.Factory<Key, ToValue> mapByPage(
+                @NonNull final Function<List<Value>, List<ToValue>> function) {
+            return new Factory<Key, ToValue>() {
+                @Override
+                public DataSource<Key, ToValue> create() {
+                    return Factory.this.create().mapByPage(function);
+                }
+            };
+        }
+    }
+
+    @NonNull
+    static <X, Y> Function<List<X>, List<Y>> createListFunction(
+            final @NonNull Function<X, Y> innerFunc) {
+        return new Function<List<X>, List<Y>>() {
+            @Override
+            public List<Y> apply(@NonNull List<X> source) {
+                List<Y> out = new ArrayList<>(source.size());
+                for (int i = 0; i < source.size(); i++) {
+                    out.add(innerFunc.apply(source.get(i)));
+                }
+                return out;
+            }
+        };
+    }
+
+    static <A, B> List<B> convert(Function<List<A>, List<B>> function, List<A> source) {
+        List<B> dest = function.apply(source);
+        if (dest.size() != source.size()) {
+            throw new IllegalStateException("Invalid Function " + function
+                    + " changed return size. This is not supported.");
+        }
+        return dest;
+    }
+
+    // Since we currently rely on implementation details of two implementations,
+    // prevent external subclassing, except through exposed subclasses
+    DataSource() {
+    }
+
+    /**
+     * Applies the given function to each value emitted by the DataSource.
+     * <p>
+     * Same as {@link #map(Function)}, but allows for batch conversions.
+     *
+     * @param function Function that runs on each loaded page, returning items of a potentially
+     *                  new type.
+     * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
+     *
+     * @return A new DataSource, which transforms items using the given function.
+     *
+     * @see #map(Function)
+     * @see DataSource.Factory#map(Function)
+     * @see DataSource.Factory#mapByPage(Function)
+     */
+    @NonNull
+    public abstract <ToValue> DataSource<Key, ToValue> mapByPage(
+            @NonNull Function<List<Value>, List<ToValue>> function);
+
+    /**
+     * Applies the given function to each value emitted by the DataSource.
+     * <p>
+     * Same as {@link #mapByPage(Function)}, but operates on individual items.
+     *
+     * @param function Function that runs on each loaded item, returning items of a potentially
+     *                  new type.
+     * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
+     *
+     * @return A new DataSource, which transforms items using the given function.
+     *
+     * @see #mapByPage(Function)
+     * @see DataSource.Factory#map(Function)
+     * @see DataSource.Factory#mapByPage(Function)
+     */
+    @NonNull
+    public abstract <ToValue> DataSource<Key, ToValue> map(
+            @NonNull Function<Value, ToValue> function);
+
+    /**
+     * Returns true if the data source guaranteed to produce a contiguous set of items,
+     * never producing gaps.
+     */
+    abstract boolean isContiguous();
+
+    static class LoadCallbackHelper<T> {
+        static void validateInitialLoadParams(@NonNull List<?> data, int position, int totalCount) {
+            if (position < 0) {
+                throw new IllegalArgumentException("Position must be non-negative");
+            }
+            if (data.size() + position > totalCount) {
+                throw new IllegalArgumentException(
+                        "List size + position too large, last item in list beyond totalCount.");
+            }
+            if (data.size() == 0 && totalCount > 0) {
+                throw new IllegalArgumentException(
+                        "Initial result cannot be empty if items are present in data set.");
+            }
+        }
+
+        @PageResult.ResultType
+        final int mResultType;
+        private final DataSource mDataSource;
+        private final PageResult.Receiver<T> mReceiver;
+
+        // mSignalLock protects mPostExecutor, and mHasSignalled
+        private final Object mSignalLock = new Object();
+        private Executor mPostExecutor = null;
+        private boolean mHasSignalled = false;
+
+        LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType,
+                @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+            mDataSource = dataSource;
+            mResultType = resultType;
+            mPostExecutor = mainThreadExecutor;
+            mReceiver = receiver;
+        }
+
+        void setPostExecutor(Executor postExecutor) {
+            synchronized (mSignalLock) {
+                mPostExecutor = postExecutor;
+            }
+        }
+
+        /**
+         * Call before verifying args, or dispatching actul results
+         *
+         * @return true if DataSource was invalid, and invalid result dispatched
+         */
+        boolean dispatchInvalidResultIfInvalid() {
+            if (mDataSource.isInvalid()) {
+                dispatchResultToReceiver(PageResult.<T>getInvalidResult());
+                return true;
+            }
+            return false;
+        }
+
+        void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
+            Executor executor;
+            synchronized (mSignalLock) {
+                if (mHasSignalled) {
+                    throw new IllegalStateException(
+                            "callback.onResult already called, cannot call again.");
+                }
+                mHasSignalled = true;
+                executor = mPostExecutor;
+            }
+
+            if (executor != null) {
+                executor.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        mReceiver.onPageResult(mResultType, result);
+                    }
+                });
+            } else {
+                mReceiver.onPageResult(mResultType, result);
+            }
+        }
+    }
+
+    /**
+     * Invalidation callback for DataSource.
+     * <p>
+     * Used to signal when a DataSource a data source has become invalid, and that a new data source
+     * is needed to continue loading data.
+     */
+    public interface InvalidatedCallback {
+        /**
+         * Called when the data backing the list has become invalid. This callback is typically used
+         * to signal that a new data source is needed.
+         * <p>
+         * This callback will be invoked on the thread that calls {@link #invalidate()}. It is valid
+         * for the data source to invalidate itself during its load methods, or for an outside
+         * source to invalidate it.
+         */
+        @AnyThread
+        void onInvalidated();
+    }
+
+    private AtomicBoolean mInvalid = new AtomicBoolean(false);
+
+    private CopyOnWriteArrayList<InvalidatedCallback> mOnInvalidatedCallbacks =
+            new CopyOnWriteArrayList<>();
+
+    /**
+     * Add a callback to invoke when the DataSource is first invalidated.
+     * <p>
+     * Once invalidated, a data source will not become valid again.
+     * <p>
+     * A data source will only invoke its callbacks once - the first time {@link #invalidate()}
+     * is called, on that thread.
+     *
+     * @param onInvalidatedCallback The callback, will be invoked on thread that
+     *                              {@link #invalidate()} is called on.
+     */
+    @AnyThread
+    @SuppressWarnings("WeakerAccess")
+    public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+        mOnInvalidatedCallbacks.add(onInvalidatedCallback);
+    }
+
+    /**
+     * Remove a previously added invalidate callback.
+     *
+     * @param onInvalidatedCallback The previously added callback.
+     */
+    @AnyThread
+    @SuppressWarnings("WeakerAccess")
+    public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+        mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
+    }
+
+    /**
+     * Signal the data source to stop loading, and notify its callback.
+     * <p>
+     * If invalidate has already been called, this method does nothing.
+     */
+    @AnyThread
+    public void invalidate() {
+        if (mInvalid.compareAndSet(false, true)) {
+            for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
+                callback.onInvalidated();
+            }
+        }
+    }
+
+    /**
+     * Returns true if the data source is invalid, and can no longer be queried for data.
+     *
+     * @return True if the data source is invalid, and can no longer return data.
+     */
+    @WorkerThread
+    public boolean isInvalid() {
+        return mInvalid.get();
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
new file mode 100644
index 0000000..5f27710
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Function;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Incremental data loader for paging keyed content, where loaded content uses previously loaded
+ * items as input to future loads.
+ * <p>
+ * Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
+ * to load item {@code N}. This is common, for example, in sorted database queries where
+ * attributes of the item such just before the next query define how to execute it.
+ * <p>
+ * The {@code InMemoryByItemRepository} in the
+ * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
+ * shows how to implement a network ItemKeyedDataSource using
+ * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
+ * handling swipe-to-refresh, network errors, and retry.
+ *
+ * @param <Key> Type of data used to query Value types out of the DataSource.
+ * @param <Value> Type of items being loaded by the DataSource.
+ */
+public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
+
+    /**
+     * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
+     *
+     * @param <Key> Type of data used to query Value types out of the DataSource.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class LoadInitialParams<Key> {
+        /**
+         * Load items around this key, or at the beginning of the data set if {@code null} is
+         * passed.
+         * <p>
+         * Note that this key is generally a hint, and may be ignored if you want to always load
+         * from the beginning.
+         */
+        @Nullable
+        public final Key requestedInitialKey;
+
+        /**
+         * Requested number of items to load.
+         * <p>
+         * Note that this may be larger than available data.
+         */
+        public final int requestedLoadSize;
+
+        /**
+         * Defines whether placeholders are enabled, and whether the total count passed to
+         * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored.
+         */
+        public final boolean placeholdersEnabled;
+
+
+        public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
+                boolean placeholdersEnabled) {
+            this.requestedInitialKey = requestedInitialKey;
+            this.requestedLoadSize = requestedLoadSize;
+            this.placeholdersEnabled = placeholdersEnabled;
+        }
+    }
+
+    /**
+     * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)}
+     * and {@link #loadAfter(LoadParams, LoadCallback)}.
+     *
+     * @param <Key> Type of data used to query Value types out of the DataSource.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class LoadParams<Key> {
+        /**
+         * Load items before/after this key.
+         * <p>
+         * Returned data must begin directly adjacent to this position.
+         */
+        public final Key key;
+        /**
+         * Requested number of items to load.
+         * <p>
+         * Returned page can be of this size, but it may be altered if that is easier, e.g. a
+         * network data source where the backend defines page size.
+         */
+        public final int requestedLoadSize;
+
+        public LoadParams(Key key, int requestedLoadSize) {
+            this.key = key;
+            this.requestedLoadSize = requestedLoadSize;
+        }
+    }
+
+    /**
+     * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
+     * to return data and, optionally, position/count information.
+     * <p>
+     * A callback can be called only once, and will throw if called again.
+     * <p>
+     * If you can compute the number of items in the data set before and after the loaded range,
+     * call the three parameter {@link #onResult(List, int, int)} to pass that information. You
+     * can skip passing this information by calling the single parameter {@link #onResult(List)},
+     * either if it's difficult to compute, or if {@link LoadInitialParams#placeholdersEnabled} is
+     * {@code false}, so the positioning information will be ignored.
+     * <p>
+     * It is always valid for a DataSource loading method that takes a callback to stash the
+     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+     * temporary, recoverable error states (such as a network error that can be retried).
+     *
+     * @param <Value> Type of items being loaded.
+     */
+    public abstract static class LoadInitialCallback<Value> extends LoadCallback<Value> {
+        /**
+         * Called to pass initial load state from a DataSource.
+         * <p>
+         * Call this method from your DataSource's {@code loadInitial} function to return data,
+         * and inform how many placeholders should be shown before and after. If counting is cheap
+         * to compute (for example, if a network load returns the information regardless), it's
+         * recommended to pass data back through this method.
+         * <p>
+         * It is always valid to pass a different amount of data than what is requested. Pass an
+         * empty list if there is no more data to load.
+         *
+         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+         *             is treated as empty, and no further loads will occur.
+         * @param position Position of the item at the front of the list. If there are {@code N}
+         *                 items before the items in data that can be loaded from this DataSource,
+         *                 pass {@code N}.
+         * @param totalCount Total number of items that may be returned from this DataSource.
+         *                   Includes the number in the initial {@code data} parameter
+         *                   as well as any items that can be loaded in front or behind of
+         *                   {@code data}.
+         */
+        public abstract void onResult(@NonNull List<Value> data, int position, int totalCount);
+    }
+
+
+    /**
+     * Callback for ItemKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)}
+     * and {@link #loadAfter(LoadParams, LoadCallback)} to return data.
+     * <p>
+     * A callback can be called only once, and will throw if called again.
+     * <p>
+     * It is always valid for a DataSource loading method that takes a callback to stash the
+     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+     * temporary, recoverable error states (such as a network error that can be retried).
+     *
+     * @param <Value> Type of items being loaded.
+     */
+    public abstract static class LoadCallback<Value> {
+        /**
+         * Called to pass loaded data from a DataSource.
+         * <p>
+         * Call this method from your ItemKeyedDataSource's
+         * {@link #loadBefore(LoadParams, LoadCallback)} and
+         * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
+         * <p>
+         * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
+         * initialize without counting available data, or supporting placeholders.
+         * <p>
+         * It is always valid to pass a different amount of data than what is requested. Pass an
+         * empty list if there is no more data to load.
+         *
+         * @param data List of items loaded from the ItemKeyedDataSource.
+         */
+        public abstract void onResult(@NonNull List<Value> data);
+    }
+
+    static class LoadInitialCallbackImpl<Value> extends LoadInitialCallback<Value> {
+        final LoadCallbackHelper<Value> mCallbackHelper;
+        private final boolean mCountingEnabled;
+        LoadInitialCallbackImpl(@NonNull ItemKeyedDataSource dataSource, boolean countingEnabled,
+                @NonNull PageResult.Receiver<Value> receiver) {
+            mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver);
+            mCountingEnabled = countingEnabled;
+        }
+
+        @Override
+        public void onResult(@NonNull List<Value> data, int position, int totalCount) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
+
+                int trailingUnloadedCount = totalCount - position - data.size();
+                if (mCountingEnabled) {
+                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
+                            data, position, trailingUnloadedCount, 0));
+                } else {
+                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
+                }
+            }
+        }
+
+        @Override
+        public void onResult(@NonNull List<Value> data) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+            }
+        }
+    }
+
+    static class LoadCallbackImpl<Value> extends LoadCallback<Value> {
+        final LoadCallbackHelper<Value> mCallbackHelper;
+
+        LoadCallbackImpl(@NonNull ItemKeyedDataSource dataSource, @PageResult.ResultType int type,
+                @Nullable Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
+            mCallbackHelper = new LoadCallbackHelper<>(
+                    dataSource, type, mainThreadExecutor, receiver);
+        }
+
+        @Override
+        public void onResult(@NonNull List<Value> data) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    final Key getKey(int position, Value item) {
+        if (item == null) {
+            return null;
+        }
+
+        return getKey(item);
+    }
+
+    @Override
+    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
+            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver) {
+        LoadInitialCallbackImpl<Value> callback =
+                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
+        loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
+
+        // If initialLoad's callback is not called within the body, we force any following calls
+        // to post to the UI thread. This constructor may be run on a background thread, but
+        // after constructor, mutation must happen on UI thread.
+        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
+    }
+
+    @Override
+    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
+            int pageSize, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver) {
+        loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize),
+                new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
+    }
+
+    @Override
+    final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
+            int pageSize, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver) {
+        loadBefore(new LoadParams<>(getKey(currentBeginItem), pageSize),
+                new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
+    }
+
+    /**
+     * Load initial data.
+     * <p>
+     * This method is called first to initialize a PagedList with data. If it's possible to count
+     * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
+     * the callback via the three-parameter
+     * {@link LoadInitialCallback#onResult(List, int, int)}. This enables PagedLists
+     * presenting data from this source to display placeholders to represent unloaded items.
+     * <p>
+     * {@link LoadInitialParams#requestedInitialKey} and {@link LoadInitialParams#requestedLoadSize}
+     * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
+     * {@code requestedInitialKey} can prevent subsequent PagedList/DataSource pairs from
+     * initializing at the same location. If your data source never invalidates (for example,
+     * loading from the network without the network ever signalling that old data must be reloaded),
+     * it's fine to ignore the {@code initialLoadKey} and always start from the beginning of the
+     * data set.
+     *
+     * @param params Parameters for initial load, including initial key and requested size.
+     * @param callback Callback that receives initial load data.
+     */
+    public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
+            @NonNull LoadInitialCallback<Value> callback);
+
+    /**
+     * Load list data after the key specified in {@link LoadParams#key LoadParams.key}.
+     * <p>
+     * It's valid to return a different list size than the page size if it's easier, e.g. if your
+     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+     * <p>
+     * Data may be passed synchronously during the loadAfter method, or deferred and called at a
+     * later time. Further loads going down will be blocked until the callback is called.
+     * <p>
+     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+     * and prevent further loading.
+     *
+     * @param params Parameters for the load, including the key to load after, and requested size.
+     * @param callback Callback that receives loaded data.
+     */
+    public abstract void loadAfter(@NonNull LoadParams<Key> params,
+            @NonNull LoadCallback<Value> callback);
+
+    /**
+     * Load list data before the key specified in {@link LoadParams#key LoadParams.key}.
+     * <p>
+     * It's valid to return a different list size than the page size if it's easier, e.g. if your
+     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+     * <p>
+     * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
+     * passed, so if you vary size, ensure that the last item is adjacent to the passed key.
+     * <p>
+     * Data may be passed synchronously during the loadBefore method, or deferred and called at a
+     * later time. Further loads going up will be blocked until the callback is called.
+     * <p>
+     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+     * and prevent further loading.
+     *
+     * @param params Parameters for the load, including the key to load before, and requested size.
+     * @param callback Callback that receives loaded data.
+     */
+    public abstract void loadBefore(@NonNull LoadParams<Key> params,
+            @NonNull LoadCallback<Value> callback);
+
+    /**
+     * Return a key associated with the given item.
+     * <p>
+     * If your ItemKeyedDataSource 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(LoadParams, LoadCallback)} or
+     * {@link #loadAfter(LoadParams, LoadCallback)} 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
+    public abstract Key getKey(@NonNull Value item);
+
+    @NonNull
+    @Override
+    public final <ToValue> ItemKeyedDataSource<Key, ToValue> mapByPage(
+            @NonNull Function<List<Value>, List<ToValue>> function) {
+        return new WrapperItemKeyedDataSource<>(this, function);
+    }
+
+    @NonNull
+    @Override
+    public final <ToValue> ItemKeyedDataSource<Key, ToValue> map(
+            @NonNull Function<Value, ToValue> function) {
+        return mapByPage(createListFunction(function));
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/ListDataSource.java b/paging/common/src/main/java/androidx/paging/ListDataSource.java
new file mode 100644
index 0000000..481d5f0
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/ListDataSource.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class ListDataSource<T> extends PositionalDataSource<T> {
+    private final List<T> mList;
+
+    public ListDataSource(List<T> list) {
+        mList = new ArrayList<>(list);
+    }
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams params,
+            @NonNull LoadInitialCallback<T> callback) {
+        final int totalCount = mList.size();
+
+        final int position = computeInitialLoadPosition(params, totalCount);
+        final int loadSize = computeInitialLoadSize(params, position, totalCount);
+
+        // for simplicity, we could return everything immediately,
+        // but we tile here since it's expected behavior
+        List<T> sublist = mList.subList(position, position + loadSize);
+        callback.onResult(sublist, position, totalCount);
+    }
+
+    @Override
+    public void loadRange(@NonNull LoadRangeParams params,
+            @NonNull LoadRangeCallback<T> callback) {
+        callback.onResult(mList.subList(params.startPosition,
+                params.startPosition + params.loadSize));
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
new file mode 100644
index 0000000..db6dddf
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Function;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Incremental data loader for page-keyed content, where requests return keys for next/previous
+ * pages.
+ * <p>
+ * Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
+ * to load page {@code N}. This is common, for example, in network APIs that include a next/previous
+ * link or key with each page load.
+ * <p>
+ * The {@code InMemoryByPageRepository} in the
+ * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
+ * shows how to implement a network PageKeyedDataSource using
+ * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
+ * handling swipe-to-refresh, network errors, and retry.
+ *
+ * @param <Key> Type of data used to query Value types out of the DataSource.
+ * @param <Value> Type of items being loaded by the DataSource.
+ */
+public abstract class PageKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
+    private final Object mKeyLock = new Object();
+
+    @Nullable
+    @GuardedBy("mKeyLock")
+    private Key mNextKey = null;
+
+    @Nullable
+    @GuardedBy("mKeyLock")
+    private Key mPreviousKey = null;
+
+    private void initKeys(@Nullable Key previousKey, @Nullable Key nextKey) {
+        synchronized (mKeyLock) {
+            mPreviousKey = previousKey;
+            mNextKey = nextKey;
+        }
+    }
+
+    private void setPreviousKey(@Nullable Key previousKey) {
+        synchronized (mKeyLock) {
+            mPreviousKey = previousKey;
+        }
+    }
+
+    private void setNextKey(@Nullable Key nextKey) {
+        synchronized (mKeyLock) {
+            mNextKey = nextKey;
+        }
+    }
+
+    private @Nullable Key getPreviousKey() {
+        synchronized (mKeyLock) {
+            return mPreviousKey;
+        }
+    }
+
+    private @Nullable Key getNextKey() {
+        synchronized (mKeyLock) {
+            return mNextKey;
+        }
+    }
+
+    /**
+     * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
+     *
+     * @param <Key> Type of data used to query pages.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class LoadInitialParams<Key> {
+        /**
+         * Requested number of items to load.
+         * <p>
+         * Note that this may be larger than available data.
+         */
+        public final int requestedLoadSize;
+
+        /**
+         * Defines whether placeholders are enabled, and whether the total count passed to
+         * {@link LoadInitialCallback#onResult(List, int, int, Key, Key)} will be ignored.
+         */
+        public final boolean placeholdersEnabled;
+
+
+        public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
+            this.requestedLoadSize = requestedLoadSize;
+            this.placeholdersEnabled = placeholdersEnabled;
+        }
+    }
+
+    /**
+     * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)} and
+     * {@link #loadAfter(LoadParams, LoadCallback)}.
+     *
+     * @param <Key> Type of data used to query pages.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class LoadParams<Key> {
+        /**
+         * Load items before/after this key.
+         * <p>
+         * Returned data must begin directly adjacent to this position.
+         */
+        public final Key key;
+
+        /**
+         * Requested number of items to load.
+         * <p>
+         * Returned page can be of this size, but it may be altered if that is easier, e.g. a
+         * network data source where the backend defines page size.
+         */
+        public final int requestedLoadSize;
+
+        public LoadParams(Key key, int requestedLoadSize) {
+            this.key = key;
+            this.requestedLoadSize = requestedLoadSize;
+        }
+    }
+
+    /**
+     * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
+     * to return data and, optionally, position/count information.
+     * <p>
+     * A callback can be called only once, and will throw if called again.
+     * <p>
+     * If you can compute the number of items in the data set before and after the loaded range,
+     * call the five parameter {@link #onResult(List, int, int, Object, Object)} to pass that
+     * information. You can skip passing this information by calling the three parameter
+     * {@link #onResult(List, Object, Object)}, either if it's difficult to compute, or if
+     * {@link LoadInitialParams#placeholdersEnabled} is {@code false}, so the positioning
+     * information will be ignored.
+     * <p>
+     * It is always valid for a DataSource loading method that takes a callback to stash the
+     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+     * temporary, recoverable error states (such as a network error that can be retried).
+     *
+     * @param <Key> Type of data used to query pages.
+     * @param <Value> Type of items being loaded.
+     */
+    public abstract static class LoadInitialCallback<Key, Value> {
+        /**
+         * Called to pass initial load state from a DataSource.
+         * <p>
+         * Call this method from your DataSource's {@code loadInitial} function to return data,
+         * and inform how many placeholders should be shown before and after. If counting is cheap
+         * to compute (for example, if a network load returns the information regardless), it's
+         * recommended to pass data back through this method.
+         * <p>
+         * It is always valid to pass a different amount of data than what is requested. Pass an
+         * empty list if there is no more data to load.
+         *
+         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+         *             is treated as empty, and no further loads will occur.
+         * @param position Position of the item at the front of the list. If there are {@code N}
+         *                 items before the items in data that can be loaded from this DataSource,
+         *                 pass {@code N}.
+         * @param totalCount Total number of items that may be returned from this DataSource.
+         *                   Includes the number in the initial {@code data} parameter
+         *                   as well as any items that can be loaded in front or behind of
+         *                   {@code data}.
+         */
+        public abstract void onResult(@NonNull List<Value> data, int position, int totalCount,
+                @Nullable Key previousPageKey, @Nullable Key nextPageKey);
+
+        /**
+         * Called to pass loaded data from a DataSource.
+         * <p>
+         * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
+         * initialize without counting available data, or supporting placeholders.
+         * <p>
+         * It is always valid to pass a different amount of data than what is requested. Pass an
+         * empty list if there is no more data to load.
+         *
+         * @param data List of items loaded from the PageKeyedDataSource.
+         * @param previousPageKey Key for page before the initial load result, or {@code null} if no
+         *                        more data can be loaded before.
+         * @param nextPageKey Key for page after the initial load result, or {@code null} if no
+         *                        more data can be loaded after.
+         */
+        public abstract void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
+                @Nullable Key nextPageKey);
+    }
+
+    /**
+     * Callback for PageKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)} and
+     * {@link #loadAfter(LoadParams, LoadCallback)} to return data.
+     * <p>
+     * A callback can be called only once, and will throw if called again.
+     * <p>
+     * It is always valid for a DataSource loading method that takes a callback to stash the
+     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+     * temporary, recoverable error states (such as a network error that can be retried).
+     *
+     * @param <Key> Type of data used to query pages.
+     * @param <Value> Type of items being loaded.
+     */
+    public abstract static class LoadCallback<Key, Value> {
+
+        /**
+         * Called to pass loaded data from a DataSource.
+         * <p>
+         * Call this method from your PageKeyedDataSource's
+         * {@link #loadBefore(LoadParams, LoadCallback)} and
+         * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
+         * <p>
+         * It is always valid to pass a different amount of data than what is requested. Pass an
+         * empty list if there is no more data to load.
+         * <p>
+         * Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've
+         * loaded a page in {@link #loadBefore(LoadParams, LoadCallback)}, pass the key for the
+         * previous page, or {@code null} if the loaded page is the first. If in
+         * {@link #loadAfter(LoadParams, LoadCallback)}, pass the key for the next page, or
+         * {@code null} if the loaded page is the last.
+         *
+         * @param data List of items loaded from the PageKeyedDataSource.
+         * @param adjacentPageKey Key for subsequent page load (previous page in {@link #loadBefore}
+         *                        / next page in {@link #loadAfter}), or {@code null} if there are
+         *                        no more pages to load in the current load direction.
+         */
+        public abstract void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey);
+    }
+
+    static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
+        final LoadCallbackHelper<Value> mCallbackHelper;
+        private final PageKeyedDataSource<Key, Value> mDataSource;
+        private final boolean mCountingEnabled;
+        LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
+                boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
+            mCallbackHelper = new LoadCallbackHelper<>(
+                    dataSource, PageResult.INIT, null, receiver);
+            mDataSource = dataSource;
+            mCountingEnabled = countingEnabled;
+        }
+
+        @Override
+        public void onResult(@NonNull List<Value> data, int position, int totalCount,
+                @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
+
+                // setup keys before dispatching data, so guaranteed to be ready
+                mDataSource.initKeys(previousPageKey, nextPageKey);
+
+                int trailingUnloadedCount = totalCount - position - data.size();
+                if (mCountingEnabled) {
+                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
+                            data, position, trailingUnloadedCount, 0));
+                } else {
+                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
+                }
+            }
+        }
+
+        @Override
+        public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
+                @Nullable Key nextPageKey) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                mDataSource.initKeys(previousPageKey, nextPageKey);
+                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+            }
+        }
+    }
+
+    static class LoadCallbackImpl<Key, Value> extends LoadCallback<Key, Value> {
+        final LoadCallbackHelper<Value> mCallbackHelper;
+        private final PageKeyedDataSource<Key, Value> mDataSource;
+        LoadCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
+                @PageResult.ResultType int type, @Nullable Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
+            mCallbackHelper = new LoadCallbackHelper<>(
+                    dataSource, type, mainThreadExecutor, receiver);
+            mDataSource = dataSource;
+        }
+
+        @Override
+        public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                if (mCallbackHelper.mResultType == PageResult.APPEND) {
+                    mDataSource.setNextKey(adjacentPageKey);
+                } else {
+                    mDataSource.setPreviousKey(adjacentPageKey);
+                }
+                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    final Key getKey(int position, Value item) {
+        // don't attempt to persist keys, since we currently don't pass them to initial load
+        return null;
+    }
+
+    @Override
+    final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
+            boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver) {
+        LoadInitialCallbackImpl<Key, Value> callback =
+                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
+        loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
+
+        // If initialLoad's callback is not called within the body, we force any following calls
+        // to post to the UI thread. This constructor may be run on a background thread, but
+        // after constructor, mutation must happen on UI thread.
+        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
+    }
+
+
+    @Override
+    final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
+            int pageSize, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver) {
+        @Nullable Key key = getNextKey();
+        if (key != null) {
+            loadAfter(new LoadParams<>(key, pageSize),
+                    new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
+        }
+    }
+
+    @Override
+    final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
+            int pageSize, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<Value> receiver) {
+        @Nullable Key key = getPreviousKey();
+        if (key != null) {
+            loadBefore(new LoadParams<>(key, pageSize),
+                    new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
+        }
+    }
+
+    /**
+     * Load initial data.
+     * <p>
+     * This method is called first to initialize a PagedList with data. If it's possible to count
+     * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
+     * the callback via the three-parameter
+     * {@link LoadInitialCallback#onResult(List, int, int, Object, Object)}. This enables PagedLists
+     * presenting data from this source to display placeholders to represent unloaded items.
+     * <p>
+     * {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement, so it may be may be
+     * altered or ignored.
+     *
+     * @param params Parameters for initial load, including requested load size.
+     * @param callback Callback that receives initial load data.
+     */
+    public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
+            @NonNull LoadInitialCallback<Key, Value> callback);
+
+    /**
+     * Prepend page with the key specified by {@link LoadParams#key LoadParams.key}.
+     * <p>
+     * It's valid to return a different list size than the page size if it's easier, e.g. if your
+     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+     * <p>
+     * Data may be passed synchronously during the load method, or deferred and called at a
+     * later time. Further loads going down will be blocked until the callback is called.
+     * <p>
+     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+     * and prevent further loading.
+     *
+     * @param params Parameters for the load, including the key for the new page, and requested load
+     *               size.
+     * @param callback Callback that receives loaded data.
+     */
+    public abstract void loadBefore(@NonNull LoadParams<Key> params,
+            @NonNull LoadCallback<Key, Value> callback);
+
+    /**
+     * Append page with the key specified by {@link LoadParams#key LoadParams.key}.
+     * <p>
+     * It's valid to return a different list size than the page size if it's easier, e.g. if your
+     * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+     * <p>
+     * Data may be passed synchronously during the load method, or deferred and called at a
+     * later time. Further loads going down will be blocked until the callback is called.
+     * <p>
+     * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+     * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+     * and prevent further loading.
+     *
+     * @param params Parameters for the load, including the key for the new page, and requested load
+     *               size.
+     * @param callback Callback that receives loaded data.
+     */
+    public abstract void loadAfter(@NonNull LoadParams<Key> params,
+            @NonNull LoadCallback<Key, Value> callback);
+
+    @NonNull
+    @Override
+    public final <ToValue> PageKeyedDataSource<Key, ToValue> mapByPage(
+            @NonNull Function<List<Value>, List<ToValue>> function) {
+        return new WrapperPageKeyedDataSource<>(this, function);
+    }
+
+    @NonNull
+    @Override
+    public final <ToValue> PageKeyedDataSource<Key, ToValue> map(
+            @NonNull Function<Value, ToValue> function) {
+        return mapByPage(createListFunction(function));
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/PageResult.java b/paging/common/src/main/java/androidx/paging/PageResult.java
new file mode 100644
index 0000000..1f7054b
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/PageResult.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.util.Collections;
+import java.util.List;
+
+class PageResult<T> {
+    @SuppressWarnings("unchecked")
+    private static final PageResult INVALID_RESULT =
+            new PageResult(Collections.EMPTY_LIST, 0);
+
+    @SuppressWarnings("unchecked")
+    static <T> PageResult<T> getInvalidResult() {
+        return INVALID_RESULT;
+    }
+
+
+    @Retention(SOURCE)
+    @IntDef({INIT, APPEND, PREPEND, TILE})
+    @interface ResultType {}
+
+    static final int INIT = 0;
+
+    // contiguous results
+    static final int APPEND = 1;
+    static final int PREPEND = 2;
+
+    // non-contiguous, tile result
+    static final int TILE = 3;
+
+    @NonNull
+    public final List<T> page;
+    @SuppressWarnings("WeakerAccess")
+    public final int leadingNulls;
+    @SuppressWarnings("WeakerAccess")
+    public final int trailingNulls;
+    @SuppressWarnings("WeakerAccess")
+    public final int positionOffset;
+
+    PageResult(@NonNull List<T> list, int leadingNulls, int trailingNulls, int positionOffset) {
+        this.page = list;
+        this.leadingNulls = leadingNulls;
+        this.trailingNulls = trailingNulls;
+        this.positionOffset = positionOffset;
+    }
+
+    PageResult(@NonNull List<T> list, int positionOffset) {
+        this.page = list;
+        this.leadingNulls = 0;
+        this.trailingNulls = 0;
+        this.positionOffset = positionOffset;
+    }
+
+    @Override
+    public String toString() {
+        return "Result " + leadingNulls
+                + ", " + page
+                + ", " + trailingNulls
+                + ", offset " + positionOffset;
+    }
+
+    public boolean isInvalid() {
+        return this == INVALID_RESULT;
+    }
+
+    abstract static class Receiver<T> {
+        @MainThread
+        public abstract void onPageResult(@ResultType int type, @NonNull PageResult<T> pageResult);
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/PagedList.java b/paging/common/src/main/java/androidx/paging/PagedList.java
new file mode 100644
index 0000000..2878290
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/PagedList.java
@@ -0,0 +1,1005 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
+
+import java.lang.ref.WeakReference;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Lazy loading list that pages in immutable content from a {@link DataSource}.
+ * <p>
+ * 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)}. To display a PagedList, see {@link PagedListAdapter}, which enables the
+ * binding of a PagedList to a {@link androidx.recyclerview.widget.RecyclerView}.
+ * <h4>Loading Data</h4>
+ * <p>
+ * All data in a PagedList is loaded from its {@link DataSource}. Creating a PagedList loads the
+ * first chunk of 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>
+ * A PagedList initially presents this first partial load as its content, and expands over time as
+ * content is loaded in. 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 LivePagedListBuilder} to get a
+ * {@link androidx.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 androidx.recyclerview.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 does not count its data set in its initial load, or if  {@code false} is passed to
+ * {@link Config.Builder#setEnablePlaceholders(boolean)} when building a {@link Config}.
+ * <h4>Mutability and Snapshots</h4>
+ * A PagedList is <em>mutable</em> while loading, or ready to load from its DataSource.
+ * As loads succeed, a mutable PagedList will be updated via Runnables on the main thread. You can
+ * listen to these updates with a {@link Callback}. (Note that {@link PagedListAdapter} will listen
+ * to these to signal RecyclerView about the updates/changes).
+ * <p>
+ * If a PagedList attempts to load from an invalid DataSource, it will {@link #detach()}
+ * from the DataSource, meaning that it will no longer attempt to load data. It will return true
+ * from {@link #isImmutable()}, and a new DataSource / PagedList pair must be created to load
+ * further data. See {@link DataSource} and {@link LivePagedListBuilder} for how new PagedLists are
+ * created to represent changed data.
+ * <p>
+ * A PagedList snapshot is simply an immutable shallow copy of the current state of the PagedList as
+ * a {@code List}. It will reference the same inner items, and contain the same {@code null}
+ * placeholders, if present.
+ *
+ * @param <T> The type of the entries in the list.
+ */
+public abstract class PagedList<T> extends AbstractList<T> {
+    @NonNull
+    final Executor mMainThreadExecutor;
+    @NonNull
+    final Executor mBackgroundThreadExecutor;
+    @Nullable
+    final BoundaryCallback<T> mBoundaryCallback;
+    @NonNull
+    final Config mConfig;
+    @NonNull
+    final PagedStorage<T> mStorage;
+
+    int mLastLoad = 0;
+    T mLastItem = null;
+
+    // if set to true, mBoundaryCallback is non-null, and should
+    // be dispatched when nearby load has occurred
+    private boolean mBoundaryCallbackBeginDeferred = false;
+    private boolean mBoundaryCallbackEndDeferred = false;
+
+    // lowest and highest index accessed by loadAround. Used to
+    // decide when mBoundaryCallback should be dispatched
+    private int mLowestIndexAccessed = Integer.MAX_VALUE;
+    private int mHighestIndexAccessed = Integer.MIN_VALUE;
+
+    private final AtomicBoolean mDetached = new AtomicBoolean(false);
+
+    protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+
+    PagedList(@NonNull PagedStorage<T> storage,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<T> boundaryCallback,
+            @NonNull Config config) {
+        mStorage = storage;
+        mMainThreadExecutor = mainThreadExecutor;
+        mBackgroundThreadExecutor = backgroundThreadExecutor;
+        mBoundaryCallback = boundaryCallback;
+        mConfig = config;
+    }
+
+    /**
+     * Create a PagedList which loads data from the provided data source on a background thread,
+     * posting updates to the main thread.
+     *
+     *
+     * @param dataSource DataSource providing data to the PagedList
+     * @param mainThreadExecutor Thread that will use and consume data from the PagedList.
+     *                           Generally, this is the UI/main thread.
+     * @param backgroundThreadExecutor Data loading will be done via this executor - should be a
+     *                                 background thread.
+     * @param boundaryCallback Optional boundary callback to attach to the list.
+     * @param config PagedList Config, which defines how the PagedList will load data.
+     * @param <K> Key type that indicates to the DataSource what data to load.
+     * @param <T> Type of items to be held and loaded by the PagedList.
+     *
+     * @return Newly created PagedList, which will page in data from the DataSource as needed.
+     */
+    @NonNull
+    private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<T> boundaryCallback,
+            @NonNull Config config,
+            @Nullable K key) {
+        if (dataSource.isContiguous() || !config.enablePlaceholders) {
+            int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
+            if (!dataSource.isContiguous()) {
+                //noinspection unchecked
+                dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
+                        .wrapAsContiguousWithoutPlaceholders();
+                if (key != null) {
+                    lastLoad = (int) key;
+                }
+            }
+            ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
+            return new ContiguousPagedList<>(contigDataSource,
+                    mainThreadExecutor,
+                    backgroundThreadExecutor,
+                    boundaryCallback,
+                    config,
+                    key,
+                    lastLoad);
+        } else {
+            return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
+                    mainThreadExecutor,
+                    backgroundThreadExecutor,
+                    boundaryCallback,
+                    config,
+                    (key != null) ? (Integer) key : 0);
+        }
+    }
+
+    /**
+     * Builder class for PagedList.
+     * <p>
+     * DataSource, Config, main thread and background executor must all be provided.
+     * <p>
+     * A PagedList queries initial data from its DataSource during construction, to avoid empty
+     * PagedLists being presented to the UI when possible. It's preferred 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>
+     * {@link LivePagedListBuilder} 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.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static final class Builder<Key, Value> {
+        private final DataSource<Key, Value> mDataSource;
+        private final Config mConfig;
+        private Executor mMainThreadExecutor;
+        private Executor mBackgroundThreadExecutor;
+        private BoundaryCallback mBoundaryCallback;
+        private Key mInitialKey;
+
+        /**
+         * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}.
+         *
+         * @param dataSource DataSource the PagedList will load from.
+         * @param config Config that defines how the PagedList loads data from its DataSource.
+         */
+        public Builder(@NonNull DataSource<Key, Value> dataSource, @NonNull Config config) {
+            //noinspection ConstantConditions
+            if (dataSource == null) {
+                throw new IllegalArgumentException("DataSource may not be null");
+            }
+            //noinspection ConstantConditions
+            if (config == null) {
+                throw new IllegalArgumentException("Config may not be null");
+            }
+            mDataSource = dataSource;
+            mConfig = config;
+        }
+
+        /**
+         * Create a PagedList.Builder with the provided {@link DataSource} and page size.
+         * <p>
+         * This method is a convenience for:
+         * <pre>
+         * PagedList.Builder(dataSource,
+         *         new PagedList.Config.Builder().setPageSize(pageSize).build());
+         * </pre>
+         *
+         * @param dataSource DataSource the PagedList will load from.
+         * @param pageSize Config that defines how the PagedList loads data from its DataSource.
+         */
+        public Builder(@NonNull DataSource<Key, Value> dataSource, int pageSize) {
+            this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build());
+        }
+        /**
+         * The executor defining where main/UI thread for page loading updates.
+         *
+         * @param mainThreadExecutor Executor for main/UI thread to receive {@link Callback} calls.
+         * @return this
+         */
+        @NonNull
+        public Builder<Key, Value> setMainThreadExecutor(@NonNull Executor mainThreadExecutor) {
+            mMainThreadExecutor = mainThreadExecutor;
+            return this;
+        }
+
+        /**
+         * The executor on which background loading will be run.
+         * <p>
+         * Does not affect initial load, which will be done on whichever thread the PagedList is
+         * created on.
+         *
+         * @param backgroundThreadExecutor Executor for background DataSource loading.
+         * @return this
+         */
+        @NonNull
+        public Builder<Key, Value> setBackgroundThreadExecutor(
+                @NonNull Executor backgroundThreadExecutor) {
+            mBackgroundThreadExecutor = backgroundThreadExecutor;
+            return this;
+        }
+
+        /**
+         * The BoundaryCallback for out of data events.
+         * <p>
+         * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load.
+         *
+         * @param boundaryCallback BoundaryCallback for listening to out-of-data events.
+         * @return this
+         */
+        @SuppressWarnings("unused")
+        @NonNull
+        public Builder<Key, Value> setBoundaryCallback(
+                @Nullable BoundaryCallback boundaryCallback) {
+            mBoundaryCallback = boundaryCallback;
+            return this;
+        }
+
+        /**
+         * Sets the initial key the DataSource should load around as part of initialization.
+         *
+         * @param initialKey Key the DataSource should load around as part of initialization.
+         * @return this
+         */
+        @NonNull
+        public Builder<Key, Value> setInitialKey(@Nullable Key initialKey) {
+            mInitialKey = initialKey;
+            return this;
+        }
+
+        /**
+         * Creates a {@link PagedList} with the given parameters.
+         * <p>
+         * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a
+         * DataSource posts all of its work (e.g. to a network thread), the PagedList will
+         * be immediately created as empty, and grow to its initial size when the initial load
+         * completes.
+         * <p>
+         * If the DataSource implements its load synchronously, doing the load work immediately in
+         * the loadInitial method, the PagedList will block on that load before completing
+         * construction. In this case, use a background thread to create a PagedList.
+         * <p>
+         * It's fine to create a PagedList with an async DataSource on the main thread, such as in
+         * the constructor of a ViewModel. An async network load won't block the initialLoad
+         * function. For a synchronous DataSource such as one created from a Room database, a
+         * {@code LiveData<PagedList>} can be safely constructed with {@link LivePagedListBuilder}
+         * on the main thread, since actual construction work is deferred, and done on a background
+         * 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
+         */
+        @WorkerThread
+        @NonNull
+        public PagedList<Value> build() {
+            // TODO: define defaults, once they can be used in module without android dependency
+            if (mMainThreadExecutor == null) {
+                throw new IllegalArgumentException("MainThreadExecutor required");
+            }
+            if (mBackgroundThreadExecutor == null) {
+                throw new IllegalArgumentException("BackgroundThreadExecutor required");
+            }
+
+            //noinspection unchecked
+            return PagedList.create(
+                    mDataSource,
+                    mMainThreadExecutor,
+                    mBackgroundThreadExecutor,
+                    mBoundaryCallback,
+                    mConfig,
+                    mInitialKey);
+        }
+    }
+
+    /**
+     * Get the item in the list of loaded items at the provided index.
+     *
+     * @param index Index in the loaded item list. Must be >= 0, and &lt; {@link #size()}
+     * @return The item at the passed index, or null if a null placeholder is at the specified
+     *         position.
+     *
+     * @see #size()
+     */
+    @Override
+    @Nullable
+    public T get(int index) {
+        T item = mStorage.get(index);
+        if (item != null) {
+            mLastItem = item;
+        }
+        return item;
+    }
+
+    /**
+     * Load adjacent items to passed index.
+     *
+     * @param index Index at which to load.
+     */
+    public void loadAround(int index) {
+        mLastLoad = index + getPositionOffset();
+        loadAroundInternal(index);
+
+        mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
+        mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
+
+        /*
+         * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
+         * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
+         * and accesses happen near the boundaries.
+         *
+         * Note: we post here, since RecyclerView may want to add items in response, and this
+         * call occurs in PagedListAdapter bind.
+         */
+        tryDispatchBoundaryCallbacks(true);
+    }
+
+    // Creation thread for initial synchronous load, otherwise main thread
+    // Safe to access main thread only state - no other thread has reference during construction
+    @AnyThread
+    void deferBoundaryCallbacks(final boolean deferEmpty,
+            final boolean deferBegin, final boolean deferEnd) {
+        if (mBoundaryCallback == null) {
+            throw new IllegalStateException("Can't defer BoundaryCallback, no instance");
+        }
+
+        /*
+         * If lowest/highest haven't been initialized, set them to storage size,
+         * since placeholders must already be computed by this point.
+         *
+         * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately
+         * if the initial load size is smaller than the prefetch window (see
+         * TiledPagedListTest#boundaryCallback_immediate())
+         */
+        if (mLowestIndexAccessed == Integer.MAX_VALUE) {
+            mLowestIndexAccessed = mStorage.size();
+        }
+        if (mHighestIndexAccessed == Integer.MIN_VALUE) {
+            mHighestIndexAccessed = 0;
+        }
+
+        if (deferEmpty || deferBegin || deferEnd) {
+            // Post to the main thread, since we may be on creation thread currently
+            mMainThreadExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    // on is dispatched immediately, since items won't be accessed
+                    //noinspection ConstantConditions
+                    if (deferEmpty) {
+                        mBoundaryCallback.onZeroItemsLoaded();
+                    }
+
+                    // for other callbacks, mark deferred, and only dispatch if loadAround
+                    // has been called near to the position
+                    if (deferBegin) {
+                        mBoundaryCallbackBeginDeferred = true;
+                    }
+                    if (deferEnd) {
+                        mBoundaryCallbackEndDeferred = true;
+                    }
+                    tryDispatchBoundaryCallbacks(false);
+                }
+            });
+        }
+    }
+
+    /**
+     * Call this when mLowest/HighestIndexAccessed are changed, or
+     * mBoundaryCallbackBegin/EndDeferred is set.
+     */
+    private void tryDispatchBoundaryCallbacks(boolean post) {
+        final boolean dispatchBegin = mBoundaryCallbackBeginDeferred
+                && mLowestIndexAccessed <= mConfig.prefetchDistance;
+        final boolean dispatchEnd = mBoundaryCallbackEndDeferred
+                && mHighestIndexAccessed >= size() - 1 - mConfig.prefetchDistance;
+
+        if (!dispatchBegin && !dispatchEnd) {
+            return;
+        }
+
+        if (dispatchBegin) {
+            mBoundaryCallbackBeginDeferred = false;
+        }
+        if (dispatchEnd) {
+            mBoundaryCallbackEndDeferred = false;
+        }
+        if (post) {
+            mMainThreadExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
+                }
+            });
+        } else {
+            dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
+        }
+    }
+
+    private void dispatchBoundaryCallbacks(boolean begin, boolean end) {
+        // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
+        if (begin) {
+            //noinspection ConstantConditions
+            mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem());
+        }
+        if (end) {
+            //noinspection ConstantConditions
+            mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem());
+        }
+    }
+
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    void offsetBoundaryAccessIndices(int offset) {
+        mLowestIndexAccessed += offset;
+        mHighestIndexAccessed += offset;
+    }
+
+    /**
+     * Returns size of the list, including any not-yet-loaded null padding.
+     *
+     * @return Current total size of the list.
+     */
+    @Override
+    public int size() {
+        return mStorage.size();
+    }
+
+    /**
+     * Returns whether the list is immutable.
+     *
+     * Immutable lists may not become mutable again, and may safely be accessed from any thread.
+     * <p>
+     * In the future, this method may return true when a PagedList has completed loading from its
+     * DataSource. Currently, it is equivalent to {@link #isDetached()}.
+     *
+     * @return True if the PagedList is immutable.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public boolean isImmutable() {
+        return isDetached();
+    }
+
+    /**
+     * Returns an immutable snapshot of the PagedList in its current state.
+     *
+     * If this PagedList {@link #isImmutable() is immutable} due to its DataSource being invalid, it
+     * will be returned.
+     *
+     * @return Immutable snapshot of PagedList data.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @NonNull
+    public List<T> snapshot() {
+        if (isImmutable()) {
+            return this;
+        }
+        return new SnapshotPagedList<>(this);
+    }
+
+    abstract boolean isContiguous();
+
+    /**
+     * Return the Config used to construct this PagedList.
+     *
+     * @return the Config of this PagedList
+     */
+    @NonNull
+    public Config getConfig() {
+        return mConfig;
+    }
+
+    /**
+     * Return the DataSource that provides data to this PagedList.
+     *
+     * @return the DataSource of this PagedList.
+     */
+    @NonNull
+    public abstract DataSource<?, T> getDataSource();
+
+    /**
+     * Return the key for the position passed most recently to {@link #loadAround(int)}.
+     * <p>
+     * When a PagedList is invalidated, you can pass the key returned by this function to initialize
+     * the next PagedList. This ensures (depending on load times) that the next PagedList that
+     * arrives will have data that overlaps. If you use {@link LivePagedListBuilder}, it will do
+     * this for you.
+     *
+     * @return Key of position most recently passed to {@link #loadAround(int)}.
+     */
+    @Nullable
+    public abstract Object getLastKey();
+
+    /**
+     * True if the PagedList has detached the DataSource it was loading from, and will no longer
+     * load new data.
+     * <p>
+     * A detached list is {@link #isImmutable() immutable}.
+     *
+     * @return True if the data source is detached.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public boolean isDetached() {
+        return mDetached.get();
+    }
+
+    /**
+     * Detach the PagedList from its DataSource, and attempt to load no more data.
+     * <p>
+     * This is called automatically when a DataSource load returns <code>null</code>, which is a
+     * signal to stop loading. The PagedList will continue to present existing data, but will not
+     * initiate new loads.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void detach() {
+        mDetached.set(true);
+    }
+
+    /**
+     * Position offset of the data in the list.
+     * <p>
+     * If data is supplied by a {@link PositionalDataSource}, the item returned from
+     * <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>.
+     * <p>
+     * If the DataSource is a {@link ItemKeyedDataSource} or {@link PageKeyedDataSource}, it
+     * doesn't use positions, returns 0.
+     */
+    public int getPositionOffset() {
+        return mStorage.getPositionOffset();
+    }
+
+    /**
+     * Adds a callback, and issues updates since the previousSnapshot was created.
+     * <p>
+     * If previousSnapshot is passed, the callback will also immediately be dispatched any
+     * differences between the previous snapshot, and the current state. For example, if the
+     * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls,
+     * 12 items, 3 nulls, the callback would immediately receive a call of
+     * <code>onChanged(14, 2)</code>.
+     * <p>
+     * This allows an observer that's currently presenting a snapshot to catch up to the most recent
+     * version, including any changes that may have been made.
+     * <p>
+     * The callback is internally held as weak reference, so PagedList doesn't hold a strong
+     * reference to its observer, such as a {@link PagedListAdapter}. If an adapter were held with a
+     * strong reference, it would be necessary to clear its PagedList observer before it could be
+     * GC'd.
+     *
+     * @param previousSnapshot Snapshot previously captured from this List, or null.
+     * @param callback Callback to dispatch to.
+     *
+     * @see #removeWeakCallback(Callback)
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
+        if (previousSnapshot != null && previousSnapshot != this) {
+
+            if (previousSnapshot.isEmpty()) {
+                if (!mStorage.isEmpty()) {
+                    // If snapshot is empty, diff is trivial - just notify number new items.
+                    // Note: occurs in async init, when snapshot taken before init page arrives
+                    callback.onInserted(0, mStorage.size());
+                }
+            } else {
+                PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
+
+                //noinspection unchecked
+                dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
+            }
+        }
+
+        // first, clean up any empty weak refs
+        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+            Callback currentCallback = mCallbacks.get(i).get();
+            if (currentCallback == null) {
+                mCallbacks.remove(i);
+            }
+        }
+
+        // then add the new one
+        mCallbacks.add(new WeakReference<>(callback));
+    }
+    /**
+     * Removes a previously added callback.
+     *
+     * @param callback Callback, previously added.
+     * @see #addWeakCallback(List, Callback)
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void removeWeakCallback(@NonNull Callback callback) {
+        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+            Callback currentCallback = mCallbacks.get(i).get();
+            if (currentCallback == null || currentCallback == callback) {
+                // found callback, or empty weak ref
+                mCallbacks.remove(i);
+            }
+        }
+    }
+
+    void notifyInserted(int position, int count) {
+        if (count != 0) {
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                Callback callback = mCallbacks.get(i).get();
+                if (callback != null) {
+                    callback.onInserted(position, count);
+                }
+            }
+        }
+    }
+
+    void notifyChanged(int position, int count) {
+        if (count != 0) {
+            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                Callback callback = mCallbacks.get(i).get();
+
+                if (callback != null) {
+                    callback.onChanged(position, count);
+                }
+            }
+        }
+    }
+
+
+
+    /**
+     * Dispatch updates since the non-empty snapshot was taken.
+     *
+     * @param snapshot Non-empty snapshot.
+     * @param callback Callback for updates that have occurred since snapshot.
+     */
+    abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
+            @NonNull Callback callback);
+
+    abstract void loadAroundInternal(int index);
+
+    /**
+     * Callback signaling when content is loaded into the list.
+     * <p>
+     * Can be used to listen to items being paged in and out. These calls will be dispatched on
+     * the executor defined by {@link Builder#setMainThreadExecutor(Executor)}, which defaults to
+     * the main/UI thread.
+     */
+    public abstract static class Callback {
+        /**
+         * Called when null padding items have been loaded to signal newly available data, or when
+         * data that hasn't been used in a while has been dropped, and swapped back to null.
+         *
+         * @param position Position of first newly loaded items, out of total number of items
+         *                 (including padded nulls).
+         * @param count    Number of items loaded.
+         */
+        public abstract void onChanged(int position, int count);
+
+        /**
+         * Called when new items have been loaded at the end or beginning of the list.
+         *
+         * @param position Position of the first newly loaded item (in practice, either
+         *                 <code>0</code> or <code>size - 1</code>.
+         * @param count    Number of items loaded.
+         */
+        public abstract void onInserted(int position, int count);
+
+        /**
+         * Called when items have been removed at the end or beginning of the list, and have not
+         * been replaced by padded nulls.
+         *
+         * @param position Position of the first newly loaded item (in practice, either
+         *                 <code>0</code> or <code>size - 1</code>.
+         * @param count    Number of items loaded.
+         */
+        @SuppressWarnings("unused")
+        public abstract void onRemoved(int position, int count);
+    }
+
+    /**
+     * Configures how a PagedList loads content from its DataSource.
+     * <p>
+     * Use a Config {@link Builder} to construct and define custom loading behavior, such as
+     * {@link Builder#setPageSize(int)}, which defines number of items loaded at a time}.
+     */
+    public static class Config {
+        /**
+         * Size of each page loaded by the PagedList.
+         */
+        public final int pageSize;
+
+        /**
+         * Prefetch distance which defines how far ahead to load.
+         * <p>
+         * If this value is set to 50, the paged list will attempt to load 50 items in advance of
+         * data that's already been accessed.
+         *
+         * @see PagedList#loadAround(int)
+         */
+        @SuppressWarnings("WeakerAccess")
+        public final int prefetchDistance;
+
+        /**
+         * Defines whether the PagedList may display null placeholders, if the DataSource provides
+         * them.
+         */
+        @SuppressWarnings("WeakerAccess")
+        public final boolean enablePlaceholders;
+
+        /**
+         * Size hint for initial load of PagedList, often larger than a regular page.
+         */
+        @SuppressWarnings("WeakerAccess")
+        public final int initialLoadSizeHint;
+
+        private Config(int pageSize, int prefetchDistance,
+                boolean enablePlaceholders, int initialLoadSizeHint) {
+            this.pageSize = pageSize;
+            this.prefetchDistance = prefetchDistance;
+            this.enablePlaceholders = enablePlaceholders;
+            this.initialLoadSizeHint = initialLoadSizeHint;
+        }
+
+        /**
+         * Builder class for {@link Config}.
+         * <p>
+         * You must at minimum specify page size with {@link #setPageSize(int)}.
+         */
+        public static final class Builder {
+            private int mPageSize = -1;
+            private int mPrefetchDistance = -1;
+            private int mInitialLoadSizeHint = -1;
+            private boolean mEnablePlaceholders = true;
+
+            /**
+             * 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
+             */
+            public Builder setPageSize(int pageSize) {
+                this.mPageSize = pageSize;
+                return this;
+            }
+
+            /**
+             * Defines how far from the edge of loaded content an access must be to trigger further
+             * 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
+             */
+            public Builder setPrefetchDistance(int prefetchDistance) {
+                this.mPrefetchDistance = prefetchDistance;
+                return this;
+            }
+
+            /**
+             * Pass false to disable null placeholders in PagedLists using this Config.
+             * <p>
+             * 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).
+             * <p>
+             * 2) placeholders are not disabled on the Config.
+             * <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
+             */
+            @SuppressWarnings("SameParameterValue")
+            public Builder setEnablePlaceholders(boolean enablePlaceholders) {
+                this.mEnablePlaceholders = enablePlaceholders;
+                return this;
+            }
+
+            /**
+             * Defines how many items to load when first load occurs.
+             * <p>
+             * This value is typically larger than page size, so on first load data there's a large
+             * enough range of content loaded to cover small scrolls.
+             * <p>
+             * When using a {@link PositionalDataSource}, the initial load size will be coerced to
+             * an integer multiple of pageSize, to enable efficient tiling.
+             * <p>
+             * If not set, defaults to three times page size.
+             *
+             * @param initialLoadSizeHint Number of items to load while initializing the PagedList.
+             * @return this
+             */
+            @SuppressWarnings("WeakerAccess")
+            public Builder setInitialLoadSizeHint(int initialLoadSizeHint) {
+                this.mInitialLoadSizeHint = initialLoadSizeHint;
+                return this;
+            }
+
+            /**
+             * Creates a {@link Config} with the given parameters.
+             *
+             * @return A new Config.
+             */
+            public Config build() {
+                if (mPageSize < 1) {
+                    throw new IllegalArgumentException("Page size must be a positive number");
+                }
+                if (mPrefetchDistance < 0) {
+                    mPrefetchDistance = mPageSize;
+                }
+                if (mInitialLoadSizeHint < 0) {
+                    mInitialLoadSizeHint = mPageSize * 3;
+                }
+                if (!mEnablePlaceholders && mPrefetchDistance == 0) {
+                    throw new IllegalArgumentException("Placeholders and prefetch are the only ways"
+                            + " to trigger loading of more data in the PagedList, so either"
+                            + " placeholders must be enabled, or prefetch distance must be > 0.");
+                }
+
+                return new Config(mPageSize, mPrefetchDistance,
+                        mEnablePlaceholders, mInitialLoadSizeHint);
+            }
+        }
+    }
+
+    /**
+     * Signals when a PagedList has reached the end of available data.
+     * <p>
+     * When local storage is a cache of network data, it's common to set up a streaming pipeline:
+     * Network data is paged into the database, database is paged into UI. Paging from the database
+     * to UI can be done with a {@code LiveData<PagedList>}, but it's still necessary to know when
+     * to trigger network loads.
+     * <p>
+     * BoundaryCallback does this signaling - when a DataSource runs out of data at the end of
+     * the list, {@link #onItemAtEndLoaded(Object)} is called, and you can start an async network
+     * load that will write the result directly to the database. Because the database is being
+     * observed, the UI bound to the {@code LiveData<PagedList>} will update automatically to
+     * account for the new items.
+     * <p>
+     * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
+     * {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple
+     * times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should
+     * avoid triggering it again while the load is ongoing.
+     * <p>
+     * BoundaryCallback only passes the item at front or end of the list. Number of items is not
+     * passed, since it may not be fully computed by the DataSource if placeholders are not
+     * supplied. Keys are not known because the BoundaryCallback is independent of the
+     * DataSource-specific keys, which may be different for local vs remote storage.
+     * <p>
+     * The database + network Repository in the
+     * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
+     * shows how to implement a network BoundaryCallback using
+     * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
+     * handling swipe-to-refresh, network errors, and retry.
+     *
+     * @param <T> Type loaded by the PagedList.
+     */
+    @MainThread
+    public abstract static class BoundaryCallback<T> {
+        /**
+         * Called when zero items are returned from an initial load of the PagedList's data source.
+         */
+        public void onZeroItemsLoaded() {}
+
+        /**
+         * Called when the item at the front of the PagedList has been loaded, and access has
+         * occurred within {@link Config#prefetchDistance} of it.
+         * <p>
+         * No more data will be prepended to the PagedList before this item.
+         *
+         * @param itemAtFront The first item of PagedList
+         */
+        public void onItemAtFrontLoaded(@NonNull T itemAtFront) {}
+
+        /**
+         * Called when the item at the end of the PagedList has been loaded, and access has
+         * occurred within {@link Config#prefetchDistance} of it.
+         * <p>
+         * No more data will be appended to the PagedList after this item.
+         *
+         * @param itemAtEnd The first item of PagedList
+         */
+        public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/PagedStorage.java b/paging/common/src/main/java/androidx/paging/PagedStorage.java
new file mode 100644
index 0000000..66f4ebf
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/PagedStorage.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+final class PagedStorage<T> extends AbstractList<T> {
+    /**
+     * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item
+     * in that position is already loading. We use a singleton placeholder list that is distinct
+     * from Collections.EMPTY_LIST for safety.
+     */
+    @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+    private static final List PLACEHOLDER_LIST = new ArrayList();
+
+    // Always set
+    private int mLeadingNullCount;
+    /**
+     * List of pages in storage.
+     *
+     * Two storage modes:
+     *
+     * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled().
+     *     Safe to access any item in any page.
+     *
+     * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
+     *     mPages may have nulls, or placeholder (empty) pages while content is loading.
+     */
+    private final ArrayList<List<T>> mPages;
+    private int mTrailingNullCount;
+
+    private int mPositionOffset;
+    /**
+     * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in
+     * {@link #mPages} may be null, but this value still counts them.
+     */
+    private int mStorageCount;
+
+    // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set
+    private int mPageSize;
+
+    private int mNumberPrepended;
+    private int mNumberAppended;
+
+    PagedStorage() {
+        mLeadingNullCount = 0;
+        mPages = new ArrayList<>();
+        mTrailingNullCount = 0;
+        mPositionOffset = 0;
+        mStorageCount = 0;
+        mPageSize = 1;
+        mNumberPrepended = 0;
+        mNumberAppended = 0;
+    }
+
+    PagedStorage(int leadingNulls, List<T> page, int trailingNulls) {
+        this();
+        init(leadingNulls, page, trailingNulls, 0);
+    }
+
+    private PagedStorage(PagedStorage<T> other) {
+        mLeadingNullCount = other.mLeadingNullCount;
+        mPages = new ArrayList<>(other.mPages);
+        mTrailingNullCount = other.mTrailingNullCount;
+        mPositionOffset = other.mPositionOffset;
+        mStorageCount = other.mStorageCount;
+        mPageSize = other.mPageSize;
+        mNumberPrepended = other.mNumberPrepended;
+        mNumberAppended = other.mNumberAppended;
+    }
+
+    PagedStorage<T> snapshot() {
+        return new PagedStorage<>(this);
+    }
+
+    private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
+        mLeadingNullCount = leadingNulls;
+        mPages.clear();
+        mPages.add(page);
+        mTrailingNullCount = trailingNulls;
+
+        mPositionOffset = positionOffset;
+        mStorageCount = page.size();
+
+        // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
+        // even if it will break if nulls convert.
+        mPageSize = page.size();
+
+        mNumberPrepended = 0;
+        mNumberAppended = 0;
+    }
+
+    void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
+            @NonNull Callback callback) {
+        init(leadingNulls, page, trailingNulls, positionOffset);
+        callback.onInitialized(size());
+    }
+
+    @Override
+    public T get(int i) {
+        if (i < 0 || i >= size()) {
+            throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
+        }
+
+        // is it definitely outside 'mPages'?
+        int localIndex = i - mLeadingNullCount;
+        if (localIndex < 0 || localIndex >= mStorageCount) {
+            return null;
+        }
+
+        int localPageIndex;
+        int pageInternalIndex;
+
+        if (isTiled()) {
+            // it's inside mPages, and we're tiled. Jump to correct tile.
+            localPageIndex = localIndex / mPageSize;
+            pageInternalIndex = localIndex % mPageSize;
+        } else {
+            // it's inside mPages, but page sizes aren't regular. Walk to correct tile.
+            // Pages can only be null while tiled, so accessing page count is safe.
+            pageInternalIndex = localIndex;
+            final int localPageCount = mPages.size();
+            for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
+                int pageSize = mPages.get(localPageIndex).size();
+                if (pageSize > pageInternalIndex) {
+                    // stop, found the page
+                    break;
+                }
+                pageInternalIndex -= pageSize;
+            }
+        }
+
+        List<T> page = mPages.get(localPageIndex);
+        if (page == null || page.size() == 0) {
+            // can only occur in tiled case, with untouched inner/placeholder pages
+            return null;
+        }
+        return page.get(pageInternalIndex);
+    }
+
+    /**
+     * Returns true if all pages are the same size, except for the last, which may be smaller
+     */
+    boolean isTiled() {
+        return mPageSize > 0;
+    }
+
+    int getLeadingNullCount() {
+        return mLeadingNullCount;
+    }
+
+    int getTrailingNullCount() {
+        return mTrailingNullCount;
+    }
+
+    int getStorageCount() {
+        return mStorageCount;
+    }
+
+    int getNumberAppended() {
+        return mNumberAppended;
+    }
+
+    int getNumberPrepended() {
+        return mNumberPrepended;
+    }
+
+    int getPageCount() {
+        return mPages.size();
+    }
+
+    interface Callback {
+        void onInitialized(int count);
+        void onPagePrepended(int leadingNulls, int changed, int added);
+        void onPageAppended(int endPosition, int changed, int added);
+        void onPagePlaceholderInserted(int pageIndex);
+        void onPageInserted(int start, int count);
+    }
+
+    int getPositionOffset() {
+        return mPositionOffset;
+    }
+
+    @Override
+    public int size() {
+        return mLeadingNullCount + mStorageCount + mTrailingNullCount;
+    }
+
+    int computeLeadingNulls() {
+        int total = mLeadingNullCount;
+        final int pageCount = mPages.size();
+        for (int i = 0; i < pageCount; i++) {
+            List page = mPages.get(i);
+            if (page != null && page != PLACEHOLDER_LIST) {
+                break;
+            }
+            total += mPageSize;
+        }
+        return total;
+    }
+
+    int computeTrailingNulls() {
+        int total = mTrailingNullCount;
+        for (int i = mPages.size() - 1; i >= 0; i--) {
+            List page = mPages.get(i);
+            if (page != null && page != PLACEHOLDER_LIST) {
+                break;
+            }
+            total += mPageSize;
+        }
+        return total;
+    }
+
+    // ---------------- Contiguous API -------------------
+
+    T getFirstLoadedItem() {
+        // safe to access first page's first item here:
+        // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+        return mPages.get(0).get(0);
+    }
+
+    T getLastLoadedItem() {
+        // safe to access last page's last item here:
+        // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+        List<T> page = mPages.get(mPages.size() - 1);
+        return page.get(page.size() - 1);
+    }
+
+    void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
+        final int count = page.size();
+        if (count == 0) {
+            // Nothing returned from source, stop loading in this direction
+            return;
+        }
+        if (mPageSize > 0 && count != mPageSize) {
+            if (mPages.size() == 1 && count > mPageSize) {
+                // prepending to a single item - update current page size to that of 'inner' page
+                mPageSize = count;
+            } else {
+                // no longer tiled
+                mPageSize = -1;
+            }
+        }
+
+        mPages.add(0, page);
+        mStorageCount += count;
+
+        final int changedCount = Math.min(mLeadingNullCount, count);
+        final int addedCount = count - changedCount;
+
+        if (changedCount != 0) {
+            mLeadingNullCount -= changedCount;
+        }
+        mPositionOffset -= addedCount;
+        mNumberPrepended += count;
+
+        callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
+    }
+
+    void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
+        final int count = page.size();
+        if (count == 0) {
+            // Nothing returned from source, stop loading in this direction
+            return;
+        }
+
+        if (mPageSize > 0) {
+            // if the previous page was smaller than mPageSize,
+            // or if this page is larger than the previous, disable tiling
+            if (mPages.get(mPages.size() - 1).size() != mPageSize
+                    || count > mPageSize) {
+                mPageSize = -1;
+            }
+        }
+
+        mPages.add(page);
+        mStorageCount += count;
+
+        final int changedCount = Math.min(mTrailingNullCount, count);
+        final int addedCount = count - changedCount;
+
+        if (changedCount != 0) {
+            mTrailingNullCount -= changedCount;
+        }
+        mNumberAppended += count;
+        callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
+                changedCount, addedCount);
+    }
+
+    // ------------------ Non-Contiguous API (tiling required) ----------------------
+
+    void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
+            int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
+
+        int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize;
+        for (int i = 0; i < pageCount; i++) {
+            int beginInclusive = i * pageSize;
+            int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize);
+
+            List<T> sublist = multiPageList.subList(beginInclusive, endExclusive);
+
+            if (i == 0) {
+                // Trailing nulls for first page includes other pages in multiPageList
+                int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size();
+                init(leadingNulls, sublist, initialTrailingNulls, positionOffset);
+            } else {
+                int insertPosition = leadingNulls + beginInclusive;
+                insertPage(insertPosition, sublist, null);
+            }
+        }
+        callback.onInitialized(size());
+    }
+
+    public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) {
+        final int newPageSize = page.size();
+        if (newPageSize != mPageSize) {
+            // differing page size is OK in 2 cases, when the page is being added:
+            // 1) to the end (in which case, ignore new smaller size)
+            // 2) only the last page has been added so far (in which case, adopt new bigger size)
+
+            int size = size();
+            boolean addingLastPage = position == (size - size % mPageSize)
+                    && newPageSize < mPageSize;
+            boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1
+                    && newPageSize > mPageSize;
+
+            // OK only if existing single page, and it's the last one
+            if (!onlyEndPagePresent && !addingLastPage) {
+                throw new IllegalArgumentException("page introduces incorrect tiling");
+            }
+            if (onlyEndPagePresent) {
+                mPageSize = newPageSize;
+            }
+        }
+
+        int pageIndex = position / mPageSize;
+
+        allocatePageRange(pageIndex, pageIndex);
+
+        int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
+
+        List<T> oldPage = mPages.get(localPageIndex);
+        if (oldPage != null && oldPage != PLACEHOLDER_LIST) {
+            throw new IllegalArgumentException(
+                    "Invalid position " + position + ": data already loaded");
+        }
+        mPages.set(localPageIndex, page);
+        if (callback != null) {
+            callback.onPageInserted(position, page.size());
+        }
+    }
+
+    private void allocatePageRange(final int minimumPage, final int maximumPage) {
+        int leadingNullPages = mLeadingNullCount / mPageSize;
+
+        if (minimumPage < leadingNullPages) {
+            for (int i = 0; i < leadingNullPages - minimumPage; i++) {
+                mPages.add(0, null);
+            }
+            int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize;
+            mStorageCount += newStorageAllocated;
+            mLeadingNullCount -= newStorageAllocated;
+
+            leadingNullPages = minimumPage;
+        }
+        if (maximumPage >= leadingNullPages + mPages.size()) {
+            int newStorageAllocated = Math.min(mTrailingNullCount,
+                    (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize);
+            for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) {
+                mPages.add(mPages.size(), null);
+            }
+            mStorageCount += newStorageAllocated;
+            mTrailingNullCount -= newStorageAllocated;
+        }
+    }
+
+    public void allocatePlaceholders(int index, int prefetchDistance,
+            int pageSize, Callback callback) {
+        if (pageSize != mPageSize) {
+            if (pageSize < mPageSize) {
+                throw new IllegalArgumentException("Page size cannot be reduced");
+            }
+            if (mPages.size() != 1 || mTrailingNullCount != 0) {
+                // not in single, last page allocated case - can't change page size
+                throw new IllegalArgumentException(
+                        "Page size can change only if last page is only one present");
+            }
+            mPageSize = pageSize;
+        }
+
+        final int maxPageCount = (size() + mPageSize - 1) / mPageSize;
+        int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0);
+        int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1);
+
+        allocatePageRange(minimumPage, maximumPage);
+        int leadingNullPages = mLeadingNullCount / mPageSize;
+        for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
+            int localPageIndex = pageIndex - leadingNullPages;
+            if (mPages.get(localPageIndex) == null) {
+                //noinspection unchecked
+                mPages.set(localPageIndex, PLACEHOLDER_LIST);
+                callback.onPagePlaceholderInserted(pageIndex);
+            }
+        }
+    }
+
+    public boolean hasPage(int pageSize, int index) {
+        // NOTE: we pass pageSize here to avoid in case mPageSize
+        // not fully initialized (when last page only one loaded)
+        int leadingNullPages = mLeadingNullCount / pageSize;
+
+        if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) {
+            return false;
+        }
+
+        List<T> page = mPages.get(index - leadingNullPages);
+
+        return page != null && page != PLACEHOLDER_LIST;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount
+                + ", storage " + mStorageCount
+                + ", trailing " + getTrailingNullCount());
+
+        for (int i = 0; i < mPages.size(); i++) {
+            ret.append(" ").append(mPages.get(i));
+        }
+        return ret.toString();
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/PositionalDataSource.java b/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
new file mode 100644
index 0000000..4f7cd3f
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
@@ -0,0 +1,546 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.arch.core.util.Function;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
+ * arbitrary page positions.
+ * <p>
+ * Extend PositionalDataSource if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. If your data source can't support loading arbitrary
+ * requested page sizes (e.g. when network page size constraints are only known at runtime), use
+ * either {@link PageKeyedDataSource} or {@link ItemKeyedDataSource} instead.
+ * <p>
+ * Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled}
+ * PositionalDataSource requires counting the size of the data set. This allows pages to be tiled in
+ * at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}.
+ * If placeholders are disabled, initialize with the two parameter
+ * {@link LoadInitialCallback#onResult(List, int)}.
+ * <p>
+ * Room can generate a Factory of PositionalDataSources for you:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ *     {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
+ *     public abstract DataSource.Factory&lt;Integer, User> loadUsersByAgeDesc();
+ * }</pre>
+ *
+ * @param <T> Type of items being loaded by the PositionalDataSource.
+ */
+public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
+
+    /**
+     * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class LoadInitialParams {
+        /**
+         * Initial load position requested.
+         * <p>
+         * Note that this may not be within the bounds of your data set, it may need to be adjusted
+         * before you execute your load.
+         */
+        public final int requestedStartPosition;
+
+        /**
+         * Requested number of items to load.
+         * <p>
+         * Note that this may be larger than available data.
+         */
+        public final int requestedLoadSize;
+
+        /**
+         * Defines page size acceptable for return values.
+         * <p>
+         * List of items passed to the callback must be an integer multiple of page size.
+         */
+        public final int pageSize;
+
+        /**
+         * Defines whether placeholders are enabled, and whether the total count passed to
+         * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored.
+         */
+        public final boolean placeholdersEnabled;
+
+        public LoadInitialParams(
+                int requestedStartPosition,
+                int requestedLoadSize,
+                int pageSize,
+                boolean placeholdersEnabled) {
+            this.requestedStartPosition = requestedStartPosition;
+            this.requestedLoadSize = requestedLoadSize;
+            this.pageSize = pageSize;
+            this.placeholdersEnabled = placeholdersEnabled;
+        }
+    }
+
+    /**
+     * Holder object for inputs to {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class LoadRangeParams {
+        /**
+         * Start position of data to load.
+         * <p>
+         * Returned data must start at this position.
+         */
+        public final int startPosition;
+        /**
+         * Number of items to load.
+         * <p>
+         * Returned data must be of this size, unless at end of the list.
+         */
+        public final int loadSize;
+
+        public LoadRangeParams(int startPosition, int loadSize) {
+            this.startPosition = startPosition;
+            this.loadSize = loadSize;
+        }
+    }
+
+    /**
+     * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
+     * to return data, position, and count.
+     * <p>
+     * A callback should be called only once, and may throw if called again.
+     * <p>
+     * It is always valid for a DataSource loading method that takes a callback to stash the
+     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+     * temporary, recoverable error states (such as a network error that can be retried).
+     *
+     * @param <T> Type of items being loaded.
+     */
+    public abstract static class LoadInitialCallback<T> {
+        /**
+         * Called to pass initial load state from a DataSource.
+         * <p>
+         * Call this method from your DataSource's {@code loadInitial} function to return data,
+         * and inform how many placeholders should be shown before and after. If counting is cheap
+         * to compute (for example, if a network load returns the information regardless), it's
+         * recommended to pass the total size to the totalCount parameter. If placeholders are not
+         * requested (when {@link LoadInitialParams#placeholdersEnabled} is false), you can instead
+         * call {@link #onResult(List, int)}.
+         *
+         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+         *             is treated as empty, and no further loads will occur.
+         * @param position Position of the item at the front of the list. If there are {@code N}
+         *                 items before the items in data that can be loaded from this DataSource,
+         *                 pass {@code N}.
+         * @param totalCount Total number of items that may be returned from this DataSource.
+         *                   Includes the number in the initial {@code data} parameter
+         *                   as well as any items that can be loaded in front or behind of
+         *                   {@code data}.
+         */
+        public abstract void onResult(@NonNull List<T> data, int position, int totalCount);
+
+        /**
+         * Called to pass initial load state from a DataSource without total count,
+         * when placeholders aren't requested.
+         * <p class="note"><strong>Note:</strong> This method can only be called when placeholders
+         * are disabled ({@link LoadInitialParams#placeholdersEnabled} is false).
+         * <p>
+         * Call this method from your DataSource's {@code loadInitial} function to return data,
+         * if position is known but total size is not. If placeholders are requested, call the three
+         * parameter variant: {@link #onResult(List, int, int)}.
+         *
+         * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+         *             is treated as empty, and no further loads will occur.
+         * @param position Position of the item at the front of the list. If there are {@code N}
+         *                 items before the items in data that can be provided by this DataSource,
+         *                 pass {@code N}.
+         */
+        public abstract void onResult(@NonNull List<T> data, int position);
+    }
+
+    /**
+     * Callback for PositionalDataSource {@link #loadRange(LoadRangeParams, LoadRangeCallback)}
+     * to return data.
+     * <p>
+     * A callback should be called only once, and may throw if called again.
+     * <p>
+     * It is always valid for a DataSource loading method that takes a callback to stash the
+     * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+     * temporary, recoverable error states (such as a network error that can be retried).
+     *
+     * @param <T> Type of items being loaded.
+     */
+    public abstract static class LoadRangeCallback<T> {
+        /**
+         * Called to pass loaded data from {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
+         *
+         * @param data List of items loaded from the DataSource. Must be same size as requested,
+         *             unless at end of list.
+         */
+        public abstract void onResult(@NonNull List<T> data);
+    }
+
+    static class LoadInitialCallbackImpl<T> extends LoadInitialCallback<T> {
+        final LoadCallbackHelper<T> mCallbackHelper;
+        private final boolean mCountingEnabled;
+        private final int mPageSize;
+
+        LoadInitialCallbackImpl(@NonNull PositionalDataSource dataSource, boolean countingEnabled,
+                int pageSize, PageResult.Receiver<T> receiver) {
+            mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver);
+            mCountingEnabled = countingEnabled;
+            mPageSize = pageSize;
+            if (mPageSize < 1) {
+                throw new IllegalArgumentException("Page size must be non-negative");
+            }
+        }
+
+        @Override
+        public void onResult(@NonNull List<T> data, int position, int totalCount) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
+                if (position + data.size() != totalCount
+                        && data.size() % mPageSize != 0) {
+                    throw new IllegalArgumentException("PositionalDataSource requires initial load"
+                            + " size to be a multiple of page size to support internal tiling."
+                            + " loadSize " + data.size() + ", position " + position
+                            + ", totalCount " + totalCount + ", pageSize " + mPageSize);
+                }
+
+                if (mCountingEnabled) {
+                    int trailingUnloadedCount = totalCount - position - data.size();
+                    mCallbackHelper.dispatchResultToReceiver(
+                            new PageResult<>(data, position, trailingUnloadedCount, 0));
+                } else {
+                    // Only occurs when wrapped as contiguous
+                    mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
+                }
+            }
+        }
+
+        @Override
+        public void onResult(@NonNull List<T> data, int position) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                if (position < 0) {
+                    throw new IllegalArgumentException("Position must be non-negative");
+                }
+                if (data.isEmpty() && position != 0) {
+                    throw new IllegalArgumentException(
+                            "Initial result cannot be empty if items are present in data set.");
+                }
+                if (mCountingEnabled) {
+                    throw new IllegalStateException("Placeholders requested, but totalCount not"
+                            + " provided. Please call the three-parameter onResult method, or"
+                            + " disable placeholders in the PagedList.Config");
+                }
+                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
+            }
+        }
+    }
+
+    static class LoadRangeCallbackImpl<T> extends LoadRangeCallback<T> {
+        private LoadCallbackHelper<T> mCallbackHelper;
+        private final int mPositionOffset;
+        LoadRangeCallbackImpl(@NonNull PositionalDataSource dataSource,
+                @PageResult.ResultType int resultType, int positionOffset,
+                Executor mainThreadExecutor, PageResult.Receiver<T> receiver) {
+            mCallbackHelper = new LoadCallbackHelper<>(
+                    dataSource, resultType, mainThreadExecutor, receiver);
+            mPositionOffset = positionOffset;
+        }
+
+        @Override
+        public void onResult(@NonNull List<T> data) {
+            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
+                mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
+                        data, 0, 0, mPositionOffset));
+            }
+        }
+    }
+
+    final void dispatchLoadInitial(boolean acceptCount,
+            int requestedStartPosition, int requestedLoadSize, int pageSize,
+            @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+        LoadInitialCallbackImpl<T> callback =
+                new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
+
+        LoadInitialParams params = new LoadInitialParams(
+                requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
+        loadInitial(params, callback);
+
+        // If initialLoad's callback is not called within the body, we force any following calls
+        // to post to the UI thread. This constructor may be run on a background thread, but
+        // after constructor, mutation must happen on UI thread.
+        callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
+    }
+
+    final void dispatchLoadRange(@PageResult.ResultType int resultType, int startPosition,
+            int count, @NonNull Executor mainThreadExecutor,
+            @NonNull PageResult.Receiver<T> receiver) {
+        LoadRangeCallback<T> callback = new LoadRangeCallbackImpl<>(
+                this, resultType, startPosition, mainThreadExecutor, receiver);
+        if (count == 0) {
+            callback.onResult(Collections.<T>emptyList());
+        } else {
+            loadRange(new LoadRangeParams(startPosition, count), callback);
+        }
+    }
+
+    /**
+     * Load initial list data.
+     * <p>
+     * This method is called to load the initial page(s) from the DataSource.
+     * <p>
+     * Result list must be a multiple of pageSize to enable efficient tiling.
+     *
+     * @param params Parameters for initial load, including requested start position, load size, and
+     *               page size.
+     * @param callback Callback that receives initial load data, including
+     *                 position and total data set size.
+     */
+    @WorkerThread
+    public abstract void loadInitial(
+            @NonNull LoadInitialParams params,
+            @NonNull LoadInitialCallback<T> callback);
+
+    /**
+     * Called to load a range of data from the DataSource.
+     * <p>
+     * This method is called to load additional pages from the DataSource after the
+     * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
+     * <p>
+     * Unlike {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}, this method must return
+     * the number of items requested, at the position requested.
+     *
+     * @param params Parameters for load, including start position and load size.
+     * @param callback Callback that receives loaded data.
+     */
+    @WorkerThread
+    public abstract void loadRange(@NonNull LoadRangeParams params,
+            @NonNull LoadRangeCallback<T> callback);
+
+    @Override
+    boolean isContiguous() {
+        return false;
+    }
+
+    @NonNull
+    ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
+        return new ContiguousWithoutPlaceholdersWrapper<>(this);
+    }
+
+    /**
+     * Helper for computing an initial position in
+     * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
+     * computed ahead of loading.
+     * <p>
+     * The value computed by this function will do bounds checking, page alignment, and positioning
+     * based on initial load size requested.
+     * <p>
+     * Example usage in a PositionalDataSource subclass:
+     * <pre>
+     * class ItemDataSource extends PositionalDataSource&lt;Item> {
+     *     private int computeCount() {
+     *         // actual count code here
+     *     }
+     *
+     *     private List&lt;Item> loadRangeInternal(int startPosition, int loadCount) {
+     *         // actual load code here
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadInitial({@literal @}NonNull LoadInitialParams params,
+     *             {@literal @}NonNull LoadInitialCallback&lt;Item> callback) {
+     *         int totalCount = computeCount();
+     *         int position = computeInitialLoadPosition(params, totalCount);
+     *         int loadSize = computeInitialLoadSize(params, position, totalCount);
+     *         callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadRange({@literal @}NonNull LoadRangeParams params,
+     *             {@literal @}NonNull LoadRangeCallback&lt;Item> callback) {
+     *         callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+     *     }
+     * }</pre>
+     *
+     * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
+     *               including page size, and requested start/loadSize.
+     * @param totalCount Total size of the data set.
+     * @return Position to start loading at.
+     *
+     * @see #computeInitialLoadSize(LoadInitialParams, int, int)
+     */
+    public static int computeInitialLoadPosition(@NonNull LoadInitialParams params,
+            int totalCount) {
+        int position = params.requestedStartPosition;
+        int initialLoadSize = params.requestedLoadSize;
+        int pageSize = params.pageSize;
+
+        int roundedPageStart = Math.round(position / pageSize) * pageSize;
+
+        // maximum start pos is that which will encompass end of list
+        int maximumLoadPage = ((totalCount - initialLoadSize + pageSize - 1) / pageSize) * pageSize;
+        roundedPageStart = Math.min(maximumLoadPage, roundedPageStart);
+
+        // minimum start position is 0
+        roundedPageStart = Math.max(0, roundedPageStart);
+
+        return roundedPageStart;
+    }
+
+    /**
+     * Helper for computing an initial load size in
+     * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
+     * computed ahead of loading.
+     * <p>
+     * This function takes the requested load size, and bounds checks it against the value returned
+     * by {@link #computeInitialLoadPosition(LoadInitialParams, int)}.
+     * <p>
+     * Example usage in a PositionalDataSource subclass:
+     * <pre>
+     * class ItemDataSource extends PositionalDataSource&lt;Item> {
+     *     private int computeCount() {
+     *         // actual count code here
+     *     }
+     *
+     *     private List&lt;Item> loadRangeInternal(int startPosition, int loadCount) {
+     *         // actual load code here
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadInitial({@literal @}NonNull LoadInitialParams params,
+     *             {@literal @}NonNull LoadInitialCallback&lt;Item> callback) {
+     *         int totalCount = computeCount();
+     *         int position = computeInitialLoadPosition(params, totalCount);
+     *         int loadSize = computeInitialLoadSize(params, position, totalCount);
+     *         callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+     *     }
+     *
+     *     {@literal @}Override
+     *     public void loadRange({@literal @}NonNull LoadRangeParams params,
+     *             {@literal @}NonNull LoadRangeCallback&lt;Item> callback) {
+     *         callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+     *     }
+     * }</pre>
+     *
+     * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
+     *               including page size, and requested start/loadSize.
+     * @param initialLoadPosition Value returned by
+     *                          {@link #computeInitialLoadPosition(LoadInitialParams, int)}
+     * @param totalCount Total size of the data set.
+     * @return Number of items to load.
+     *
+     * @see #computeInitialLoadPosition(LoadInitialParams, int)
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static int computeInitialLoadSize(@NonNull LoadInitialParams params,
+            int initialLoadPosition, int totalCount) {
+        return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize);
+    }
+
+    @SuppressWarnings("deprecation")
+    static class ContiguousWithoutPlaceholdersWrapper<Value>
+            extends ContiguousDataSource<Integer, Value> {
+
+        @NonNull
+        final PositionalDataSource<Value> mPositionalDataSource;
+
+        ContiguousWithoutPlaceholdersWrapper(
+                @NonNull PositionalDataSource<Value> positionalDataSource) {
+            mPositionalDataSource = positionalDataSource;
+        }
+
+        @NonNull
+        @Override
+        public <ToValue> DataSource<Integer, ToValue> mapByPage(
+                @NonNull Function<List<Value>, List<ToValue>> function) {
+            throw new UnsupportedOperationException(
+                    "Inaccessible inner type doesn't support map op");
+        }
+
+        @NonNull
+        @Override
+        public <ToValue> DataSource<Integer, ToValue> map(
+                @NonNull Function<Value, ToValue> function) {
+            throw new UnsupportedOperationException(
+                    "Inaccessible inner type doesn't support map op");
+        }
+
+        @Override
+        void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
+                boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
+            final int convertPosition = position == null ? 0 : position;
+
+            // Note enablePlaceholders will be false here, but we don't have a way to communicate
+            // this to PositionalDataSource. This is fine, because only the list and its position
+            // offset will be consumed by the LoadInitialCallback.
+            mPositionalDataSource.dispatchLoadInitial(false, convertPosition, initialLoadSize,
+                    pageSize, mainThreadExecutor, receiver);
+        }
+
+        @Override
+        void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+                @NonNull Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
+            int startIndex = currentEndIndex + 1;
+            mPositionalDataSource.dispatchLoadRange(
+                    PageResult.APPEND, startIndex, pageSize, mainThreadExecutor, receiver);
+        }
+
+        @Override
+        void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
+                int pageSize, @NonNull Executor mainThreadExecutor,
+                @NonNull PageResult.Receiver<Value> receiver) {
+
+            int startIndex = currentBeginIndex - 1;
+            if (startIndex < 0) {
+                // trigger empty list load
+                mPositionalDataSource.dispatchLoadRange(
+                        PageResult.PREPEND, startIndex, 0, mainThreadExecutor, receiver);
+            } else {
+                int loadSize = Math.min(pageSize, startIndex + 1);
+                startIndex = startIndex - loadSize + 1;
+                mPositionalDataSource.dispatchLoadRange(
+                        PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver);
+            }
+        }
+
+        @Override
+        Integer getKey(int position, Value item) {
+            return position;
+        }
+
+    }
+
+    @NonNull
+    @Override
+    public final <V> PositionalDataSource<V> mapByPage(
+            @NonNull Function<List<T>, List<V>> function) {
+        return new WrapperPositionalDataSource<>(this, function);
+    }
+
+    @NonNull
+    @Override
+    public final <V> PositionalDataSource<V> map(@NonNull Function<T, V> function) {
+        return mapByPage(createListFunction(function));
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java b/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
new file mode 100644
index 0000000..aa31b6f
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+class SnapshotPagedList<T> extends PagedList<T> {
+    private final boolean mContiguous;
+    private final Object mLastKey;
+    private final DataSource<?, T> mDataSource;
+
+    SnapshotPagedList(@NonNull PagedList<T> pagedList) {
+        super(pagedList.mStorage.snapshot(),
+                pagedList.mMainThreadExecutor,
+                pagedList.mBackgroundThreadExecutor,
+                null,
+                pagedList.mConfig);
+        mDataSource = pagedList.getDataSource();
+        mContiguous = pagedList.isContiguous();
+        mLastKey = pagedList.getLastKey();
+    }
+
+    @Override
+    public boolean isImmutable() {
+        return true;
+    }
+
+    @Override
+    public boolean isDetached() {
+        return true;
+    }
+
+    @Override
+    boolean isContiguous() {
+        return mContiguous;
+    }
+
+    @Nullable
+    @Override
+    public Object getLastKey() {
+        return mLastKey;
+    }
+
+    @NonNull
+    @Override
+    public DataSource<?, T> getDataSource() {
+        return mDataSource;
+    }
+
+    @Override
+    void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot,
+            @NonNull Callback callback) {
+    }
+
+    @Override
+    void loadAroundInternal(int index) {
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/TiledDataSource.java b/paging/common/src/main/java/androidx/paging/TiledDataSource.java
new file mode 100644
index 0000000..cc9f150
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/TiledDataSource.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
+
+import java.util.Collections;
+import java.util.List;
+
+// NOTE: Room 1.0 depends on this class, so it should not be removed until
+// we can require a version of Room that uses PositionalDataSource directly
+/**
+ * @param <T> Type loaded by the TiledDataSource.
+ *
+ * @deprecated Use {@link PositionalDataSource}
+ * @hide
+ */
+@SuppressWarnings("DeprecatedIsStillUsed")
+@Deprecated
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class TiledDataSource<T> extends PositionalDataSource<T> {
+
+    @WorkerThread
+    public abstract int countItems();
+
+    @Override
+    boolean isContiguous() {
+        return false;
+    }
+
+    @WorkerThread
+    public abstract List<T> loadRange(int startPosition, int count);
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams params,
+            @NonNull LoadInitialCallback<T> callback) {
+        int totalCount = countItems();
+        if (totalCount == 0) {
+            callback.onResult(Collections.<T>emptyList(), 0, 0);
+            return;
+        }
+
+        // bound the size requested, based on known count
+        final int firstLoadPosition = computeInitialLoadPosition(params, totalCount);
+        final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);
+
+        // convert from legacy behavior
+        List<T> list = loadRange(firstLoadPosition, firstLoadSize);
+        if (list != null && list.size() == firstLoadSize) {
+            callback.onResult(list, firstLoadPosition, totalCount);
+        } else {
+            // null list, or size doesn't match request
+            // The size check is a WAR for Room 1.0, subsequent versions do the check in Room
+            invalidate();
+        }
+    }
+
+    @Override
+    public void loadRange(@NonNull LoadRangeParams params,
+            @NonNull LoadRangeCallback<T> callback) {
+        List<T> list = loadRange(params.startPosition, params.loadSize);
+        if (list != null) {
+            callback.onResult(list);
+        } else {
+            invalidate();
+        }
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/TiledPagedList.java b/paging/common/src/main/java/androidx/paging/TiledPagedList.java
new file mode 100644
index 0000000..0c7e257
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/TiledPagedList.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import java.util.concurrent.Executor;
+
+class TiledPagedList<T> extends PagedList<T>
+        implements PagedStorage.Callback {
+    private final PositionalDataSource<T> mDataSource;
+
+    private PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() {
+        // Creation thread for initial synchronous load, otherwise main thread
+        // Safe to access main thread only state - no other thread has reference during construction
+        @AnyThread
+        @Override
+        public void onPageResult(@PageResult.ResultType int type,
+                @NonNull PageResult<T> pageResult) {
+            if (pageResult.isInvalid()) {
+                detach();
+                return;
+            }
+
+            if (isDetached()) {
+                // No op, have detached
+                return;
+            }
+
+            if (type != PageResult.INIT && type != PageResult.TILE) {
+                throw new IllegalArgumentException("unexpected resultType" + type);
+            }
+
+            if (mStorage.getPageCount() == 0) {
+                mStorage.initAndSplit(
+                        pageResult.leadingNulls, pageResult.page, pageResult.trailingNulls,
+                        pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this);
+            } else {
+                mStorage.insertPage(pageResult.positionOffset, pageResult.page,
+                        TiledPagedList.this);
+            }
+
+            if (mBoundaryCallback != null) {
+                boolean deferEmpty = mStorage.size() == 0;
+                boolean deferBegin = !deferEmpty
+                        && pageResult.leadingNulls == 0
+                        && pageResult.positionOffset == 0;
+                int size = size();
+                boolean deferEnd = !deferEmpty
+                        && ((type == PageResult.INIT && pageResult.trailingNulls == 0)
+                                || (type == PageResult.TILE
+                                        && (pageResult.positionOffset + mConfig.pageSize >= size)));
+                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
+            }
+        }
+    };
+
+    @WorkerThread
+    TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
+            @NonNull Executor mainThreadExecutor,
+            @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<T> boundaryCallback,
+            @NonNull Config config,
+            int position) {
+        super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
+                boundaryCallback, config);
+        mDataSource = dataSource;
+
+        final int pageSize = mConfig.pageSize;
+        mLastLoad = position;
+
+        if (mDataSource.isInvalid()) {
+            detach();
+        } else {
+            final int firstLoadSize =
+                    (Math.max(Math.round(mConfig.initialLoadSizeHint / pageSize), 2)) * pageSize;
+
+            final int idealStart = position - firstLoadSize / 2;
+            final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
+
+            mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize,
+                    pageSize, mMainThreadExecutor, mReceiver);
+        }
+    }
+
+    @Override
+    boolean isContiguous() {
+        return false;
+    }
+
+    @NonNull
+    @Override
+    public DataSource<?, T> getDataSource() {
+        return mDataSource;
+    }
+
+    @Nullable
+    @Override
+    public Object getLastKey() {
+        return mLastLoad;
+    }
+
+    @Override
+    protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
+            @NonNull Callback callback) {
+        //noinspection UnnecessaryLocalVariable
+        final PagedStorage<T> snapshot = pagedListSnapshot.mStorage;
+
+        if (snapshot.isEmpty()
+                || mStorage.size() != snapshot.size()) {
+            throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
+                    + " to be a snapshot of this PagedList");
+        }
+
+        // loop through each page and signal the callback for any pages that are present now,
+        // but not in the snapshot.
+        final int pageSize = mConfig.pageSize;
+        final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
+        final int pageCount = mStorage.getPageCount();
+        for (int i = 0; i < pageCount; i++) {
+            int pageIndex = i + leadingNullPages;
+            int updatedPages = 0;
+            // count number of consecutive pages that were added since the snapshot...
+            while (updatedPages < mStorage.getPageCount()
+                    && mStorage.hasPage(pageSize, pageIndex + updatedPages)
+                    && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
+                updatedPages++;
+            }
+            // and signal them all at once to the callback
+            if (updatedPages > 0) {
+                callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
+                i += updatedPages - 1;
+            }
+        }
+    }
+
+    @Override
+    protected void loadAroundInternal(int index) {
+        mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this);
+    }
+
+    @Override
+    public void onInitialized(int count) {
+        notifyInserted(0, count);
+    }
+
+    @Override
+    public void onPagePrepended(int leadingNulls, int changed, int added) {
+        throw new IllegalStateException("Contiguous callback on TiledPagedList");
+    }
+
+    @Override
+    public void onPageAppended(int endPosition, int changed, int added) {
+        throw new IllegalStateException("Contiguous callback on TiledPagedList");
+    }
+
+    @Override
+    public void onPagePlaceholderInserted(final int pageIndex) {
+        // placeholder means initialize a load
+        mBackgroundThreadExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                if (isDetached()) {
+                    return;
+                }
+                final int pageSize = mConfig.pageSize;
+
+                if (mDataSource.isInvalid()) {
+                    detach();
+                } else {
+                    int startPosition = pageIndex * pageSize;
+                    int count = Math.min(pageSize, mStorage.size() - startPosition);
+                    mDataSource.dispatchLoadRange(
+                            PageResult.TILE, startPosition, count, mMainThreadExecutor, mReceiver);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onPageInserted(int start, int count) {
+        notifyChanged(start, count);
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
new file mode 100644
index 0000000..72a6d5c
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.arch.core.util.Function;
+
+import java.util.IdentityHashMap;
+import java.util.List;
+
+class WrapperItemKeyedDataSource<K, A, B> extends ItemKeyedDataSource<K, B> {
+    private final ItemKeyedDataSource<K, A> mSource;
+    private final Function<List<A>, List<B>> mListFunction;
+    private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
+        @Override
+        public void onInvalidated() {
+            invalidate();
+            removeCallback();
+        }
+    };
+
+    private final IdentityHashMap<B, K> mKeyMap = new IdentityHashMap<>();
+
+    WrapperItemKeyedDataSource(ItemKeyedDataSource<K, A> source,
+            Function<List<A>, List<B>> listFunction) {
+        mSource = source;
+        mListFunction = listFunction;
+        mSource.addInvalidatedCallback(mInvalidatedCallback);
+    }
+
+    private void removeCallback() {
+        mSource.removeInvalidatedCallback(mInvalidatedCallback);
+    }
+
+    private List<B> convertWithStashedKeys(List<A> source) {
+        List<B> dest = convert(mListFunction, source);
+        synchronized (mKeyMap) {
+            // synchronize on mKeyMap, since multiple loads may occur simultaneously.
+            // Note: manually sync avoids locking per-item (e.g. Collections.synchronizedMap)
+            for (int i = 0; i < dest.size(); i++) {
+                mKeyMap.put(dest.get(i), mSource.getKey(source.get(i)));
+            }
+        }
+        return dest;
+    }
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams<K> params,
+            final @NonNull LoadInitialCallback<B> callback) {
+        mSource.loadInitial(params, new LoadInitialCallback<A>() {
+            @Override
+            public void onResult(@NonNull List<A> data, int position, int totalCount) {
+                callback.onResult(convertWithStashedKeys(data), position, totalCount);
+            }
+
+            @Override
+            public void onResult(@NonNull List<A> data) {
+                callback.onResult(convertWithStashedKeys(data));
+            }
+        });
+    }
+
+    @Override
+    public void loadAfter(@NonNull LoadParams<K> params,
+            final @NonNull LoadCallback<B> callback) {
+        mSource.loadAfter(params, new LoadCallback<A>() {
+            @Override
+            public void onResult(@NonNull List<A> data) {
+                callback.onResult(convertWithStashedKeys(data));
+            }
+        });
+    }
+
+    @Override
+    public void loadBefore(@NonNull LoadParams<K> params,
+            final @NonNull LoadCallback<B> callback) {
+        mSource.loadBefore(params, new LoadCallback<A>() {
+            @Override
+            public void onResult(@NonNull List<A> data) {
+                callback.onResult(convertWithStashedKeys(data));
+            }
+        });
+    }
+
+    @NonNull
+    @Override
+    public K getKey(@NonNull B item) {
+        synchronized (mKeyMap) {
+            return mKeyMap.get(item);
+        }
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
new file mode 100644
index 0000000..7675026
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Function;
+
+import java.util.List;
+
+class WrapperPageKeyedDataSource<K, A, B> extends PageKeyedDataSource<K, B> {
+    private final PageKeyedDataSource<K, A> mSource;
+    private final Function<List<A>, List<B>> mListFunction;
+    private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
+        @Override
+        public void onInvalidated() {
+            invalidate();
+            removeCallback();
+        }
+    };
+
+    WrapperPageKeyedDataSource(PageKeyedDataSource<K, A> source,
+            Function<List<A>, List<B>> listFunction) {
+        mSource = source;
+        mListFunction = listFunction;
+        mSource.addInvalidatedCallback(mInvalidatedCallback);
+    }
+
+    private void removeCallback() {
+        mSource.removeInvalidatedCallback(mInvalidatedCallback);
+    }
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams<K> params,
+            final @NonNull LoadInitialCallback<K, B> callback) {
+        mSource.loadInitial(params, new LoadInitialCallback<K, A>() {
+            @Override
+            public void onResult(@NonNull List<A> data, int position, int totalCount,
+                    @Nullable K previousPageKey, @Nullable K nextPageKey) {
+                callback.onResult(convert(mListFunction, data), position, totalCount,
+                        previousPageKey, nextPageKey);
+            }
+
+            @Override
+            public void onResult(@NonNull List<A> data, @Nullable K previousPageKey,
+                    @Nullable K nextPageKey) {
+                callback.onResult(convert(mListFunction, data), previousPageKey, nextPageKey);
+            }
+        });
+    }
+
+    @Override
+    public void loadBefore(@NonNull LoadParams<K> params,
+            final @NonNull LoadCallback<K, B> callback) {
+        mSource.loadBefore(params, new LoadCallback<K, A>() {
+            @Override
+            public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
+                callback.onResult(convert(mListFunction, data), adjacentPageKey);
+            }
+        });
+    }
+
+    @Override
+    public void loadAfter(@NonNull LoadParams<K> params,
+            final @NonNull LoadCallback<K, B> callback) {
+        mSource.loadAfter(params, new LoadCallback<K, A>() {
+            @Override
+            public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
+                callback.onResult(convert(mListFunction, data), adjacentPageKey);
+            }
+        });
+    }
+}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
new file mode 100644
index 0000000..257f6c7
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.arch.core.util.Function;
+
+import java.util.List;
+
+class WrapperPositionalDataSource<A, B> extends PositionalDataSource<B> {
+    private final PositionalDataSource<A> mSource;
+    private final Function<List<A>, List<B>> mListFunction;
+
+    private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
+        @Override
+        public void onInvalidated() {
+            invalidate();
+            removeCallback();
+        }
+    };
+
+    WrapperPositionalDataSource(PositionalDataSource<A> source,
+            Function<List<A>, List<B>> listFunction) {
+        mSource = source;
+        mListFunction = listFunction;
+        mSource.addInvalidatedCallback(mInvalidatedCallback);
+    }
+
+    private void removeCallback() {
+        mSource.removeInvalidatedCallback(mInvalidatedCallback);
+    }
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams params,
+            final @NonNull LoadInitialCallback<B> callback) {
+        mSource.loadInitial(params, new LoadInitialCallback<A>() {
+            @Override
+            public void onResult(@NonNull List<A> data, int position, int totalCount) {
+                callback.onResult(convert(mListFunction, data), position, totalCount);
+            }
+
+            @Override
+            public void onResult(@NonNull List<A> data, int position) {
+                callback.onResult(convert(mListFunction, data), position);
+            }
+        });
+    }
+
+    @Override
+    public void loadRange(@NonNull LoadRangeParams params,
+            final @NonNull LoadRangeCallback<B> callback) {
+        mSource.loadRange(params, new LoadRangeCallback<A>() {
+            @Override
+            public void onResult(@NonNull List<A> data) {
+                callback.onResult(convert(mListFunction, data));
+            }
+        });
+    }
+}
diff --git a/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt b/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
deleted file mode 100644
index e1c113d..0000000
--- a/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 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.paging
-
-class AsyncListDataSource<T>(list: List<T>)
-    : PositionalDataSource<T>() {
-    private val workItems: MutableList<() -> Unit> = ArrayList()
-    private val listDataSource = ListDataSource(list)
-
-    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
-        workItems.add {
-            listDataSource.loadInitial(params, callback)
-        }
-    }
-
-    override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
-        workItems.add {
-            listDataSource.loadRange(params, callback)
-        }
-    }
-
-    fun flush() {
-        workItems.map { it() }
-        workItems.clear()
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
deleted file mode 100644
index a0dbfb7..0000000
--- a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import android.arch.core.util.Function
-import org.junit.Assert.assertArrayEquals
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertSame
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
-import java.util.concurrent.Executor
-
-@RunWith(Parameterized::class)
-class ContiguousPagedListTest(private val mCounted: Boolean) {
-    private val mMainThread = TestExecutor()
-    private val mBackgroundThread = TestExecutor()
-
-    private class Item(position: Int) {
-        val name: String = "Item $position"
-
-        override fun toString(): String {
-            return name
-        }
-    }
-
-    private inner class TestSource(val listData: List<Item> = ITEMS)
-            : ContiguousDataSource<Int, Item>() {
-        override fun dispatchLoadInitial(
-                key: Int?,
-                initialLoadSize: Int,
-                pageSize: Int,
-                enablePlaceholders: Boolean,
-                mainThreadExecutor: Executor,
-                receiver: PageResult.Receiver<Item>) {
-
-            val convertPosition = key ?: 0
-            val position = Math.max(0, (convertPosition - initialLoadSize / 2))
-            val data = getClampedRange(position, position + initialLoadSize)
-            val trailingUnloadedCount = listData.size - position - data.size
-
-            if (enablePlaceholders && mCounted) {
-                receiver.onPageResult(PageResult.INIT,
-                        PageResult(data, position, trailingUnloadedCount, 0))
-            } else {
-                // still must pass offset, even if not counted
-                receiver.onPageResult(PageResult.INIT,
-                        PageResult(data, position))
-            }
-        }
-
-        override fun dispatchLoadAfter(
-                currentEndIndex: Int,
-                currentEndItem: Item,
-                pageSize: Int,
-                mainThreadExecutor: Executor,
-                receiver: PageResult.Receiver<Item>) {
-            val startIndex = currentEndIndex + 1
-            val data = getClampedRange(startIndex, startIndex + pageSize)
-
-            mainThreadExecutor.execute {
-                receiver.onPageResult(PageResult.APPEND, PageResult(data, 0, 0, 0))
-            }
-        }
-
-        override fun dispatchLoadBefore(
-                currentBeginIndex: Int,
-                currentBeginItem: Item,
-                pageSize: Int,
-                mainThreadExecutor: Executor,
-                receiver: PageResult.Receiver<Item>) {
-
-            val startIndex = currentBeginIndex - 1
-            val data = getClampedRange(startIndex - pageSize + 1, startIndex + 1)
-
-            mainThreadExecutor.execute {
-                receiver.onPageResult(PageResult.PREPEND, PageResult(data, 0, 0, 0))
-            }
-        }
-
-        override fun getKey(position: Int, item: Item?): Int {
-            return 0
-        }
-
-        private fun getClampedRange(startInc: Int, endExc: Int): List<Item> {
-            return listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
-        }
-
-        override fun <ToValue : Any?> mapByPage(function: Function<List<Item>, List<ToValue>>):
-                DataSource<Int, ToValue> {
-            throw UnsupportedOperationException()
-        }
-
-        override fun <ToValue : Any?> map(function: Function<Item, ToValue>):
-                DataSource<Int, ToValue> {
-            throw UnsupportedOperationException()
-        }
-    }
-
-    private fun verifyRange(start: Int, count: Int, actual: PagedStorage<Item>) {
-        if (mCounted) {
-            // assert nulls + content
-            val expected = arrayOfNulls<Item>(ITEMS.size)
-            System.arraycopy(ITEMS.toTypedArray(), start, expected, start, count)
-            assertArrayEquals(expected, actual.toTypedArray())
-
-            val expectedTrailing = ITEMS.size - start - count
-            assertEquals(ITEMS.size, actual.size)
-            assertEquals((ITEMS.size - start - expectedTrailing),
-                    actual.storageCount)
-            assertEquals(start, actual.leadingNullCount)
-            assertEquals(expectedTrailing, actual.trailingNullCount)
-        } else {
-            assertEquals(ITEMS.subList(start, start + count), actual)
-
-            assertEquals(count, actual.size)
-            assertEquals(actual.size, actual.storageCount)
-            assertEquals(0, actual.leadingNullCount)
-            assertEquals(0, actual.trailingNullCount)
-        }
-    }
-
-    private fun verifyRange(start: Int, count: Int, actual: PagedList<Item>) {
-        verifyRange(start, count, actual.mStorage)
-    }
-
-    private fun createCountedPagedList(
-            initialPosition: Int,
-            pageSize: Int = 20,
-            initLoadSize: Int = 40,
-            prefetchDistance: Int = 20,
-            listData: List<Item> = ITEMS,
-            boundaryCallback: PagedList.BoundaryCallback<Item>? = null,
-            lastLoad: Int = ContiguousPagedList.LAST_LOAD_UNSPECIFIED
-    ): ContiguousPagedList<Int, Item> {
-        return ContiguousPagedList(
-                TestSource(listData), mMainThread, mBackgroundThread, boundaryCallback,
-                PagedList.Config.Builder()
-                        .setInitialLoadSizeHint(initLoadSize)
-                        .setPageSize(pageSize)
-                        .setPrefetchDistance(prefetchDistance)
-                        .build(),
-                initialPosition,
-                lastLoad)
-    }
-
-    @Test
-    fun construct() {
-        val pagedList = createCountedPagedList(0)
-        verifyRange(0, 40, pagedList)
-    }
-
-    @Test
-    fun getDataSource() {
-        val pagedList = createCountedPagedList(0)
-        assertTrue(pagedList.dataSource is TestSource)
-
-        // snapshot keeps same DataSource
-        assertSame(pagedList.dataSource,
-                (pagedList.snapshot() as SnapshotPagedList<Item>).dataSource)
-    }
-
-    private fun verifyCallback(callback: PagedList.Callback, countedPosition: Int,
-            uncountedPosition: Int) {
-        if (mCounted) {
-            verify(callback).onChanged(countedPosition, 20)
-        } else {
-            verify(callback).onInserted(uncountedPosition, 20)
-        }
-    }
-
-    @Test
-    fun append() {
-        val pagedList = createCountedPagedList(0)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyRange(0, 40, pagedList)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(35)
-        drain()
-
-        verifyRange(0, 60, pagedList)
-        verifyCallback(callback, 40, 40)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun prepend() {
-        val pagedList = createCountedPagedList(80)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyRange(60, 40, pagedList)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(if (mCounted) 65 else 5)
-        drain()
-
-        verifyRange(40, 60, pagedList)
-        verifyCallback(callback, 40, 0)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun outwards() {
-        val pagedList = createCountedPagedList(50)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyRange(30, 40, pagedList)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(if (mCounted) 65 else 35)
-        drain()
-
-        verifyRange(30, 60, pagedList)
-        verifyCallback(callback, 70, 40)
-        verifyNoMoreInteractions(callback)
-
-        pagedList.loadAround(if (mCounted) 35 else 5)
-        drain()
-
-        verifyRange(10, 80, pagedList)
-        verifyCallback(callback, 10, 0)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun multiAppend() {
-        val pagedList = createCountedPagedList(0)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyRange(0, 40, pagedList)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(55)
-        drain()
-
-        verifyRange(0, 80, pagedList)
-        verifyCallback(callback, 40, 40)
-        verifyCallback(callback, 60, 60)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun distantPrefetch() {
-        val pagedList = createCountedPagedList(0,
-                initLoadSize = 10, pageSize = 10, prefetchDistance = 30)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyRange(0, 10, pagedList)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(5)
-        drain()
-
-        verifyRange(0, 40, pagedList)
-
-        pagedList.loadAround(6)
-        drain()
-
-        // although our prefetch window moves forward, no new load triggered
-        verifyRange(0, 40, pagedList)
-    }
-
-    @Test
-    fun appendCallbackAddedLate() {
-        val pagedList = createCountedPagedList(0)
-        verifyRange(0, 40, pagedList)
-
-        pagedList.loadAround(35)
-        drain()
-        verifyRange(0, 60, pagedList)
-
-        // snapshot at 60 items
-        val snapshot = pagedList.snapshot() as PagedList<Item>
-        verifyRange(0, 60, snapshot)
-
-        // load more items...
-        pagedList.loadAround(55)
-        drain()
-        verifyRange(0, 80, pagedList)
-        verifyRange(0, 60, snapshot)
-
-        // and verify the snapshot hasn't received them
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(snapshot, callback)
-        verifyCallback(callback, 60, 60)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun prependCallbackAddedLate() {
-        val pagedList = createCountedPagedList(80)
-        verifyRange(60, 40, pagedList)
-
-        pagedList.loadAround(if (mCounted) 65 else 5)
-        drain()
-        verifyRange(40, 60, pagedList)
-
-        // snapshot at 60 items
-        val snapshot = pagedList.snapshot() as PagedList<Item>
-        verifyRange(40, 60, snapshot)
-
-        pagedList.loadAround(if (mCounted) 45 else 5)
-        drain()
-        verifyRange(20, 80, pagedList)
-        verifyRange(40, 60, snapshot)
-
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(snapshot, callback)
-        verifyCallback(callback, 40, 0)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun initialLoad_lastLoad() {
-        val pagedList = createCountedPagedList(
-                initialPosition = 0,
-                initLoadSize = 20,
-                lastLoad = 4)
-        // last load is param passed
-        assertEquals(4, pagedList.mLastLoad)
-        verifyRange(0, 20, pagedList)
-    }
-
-    @Test
-    fun initialLoad_lastLoadComputed() {
-        val pagedList = createCountedPagedList(
-                initialPosition = 0,
-                initLoadSize = 20,
-                lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-        // last load is middle of initial load
-        assertEquals(10, pagedList.mLastLoad)
-        verifyRange(0, 20, pagedList)
-    }
-
-    @Test
-    fun initialLoadAsync() {
-        // Note: ignores Parameterized param
-        val asyncDataSource = AsyncListDataSource(ITEMS)
-        val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
-        val pagedList = ContiguousPagedList(
-                dataSource, mMainThread, mBackgroundThread, null,
-                PagedList.Config.Builder().setPageSize(10).build(), null,
-                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-
-        assertTrue(pagedList.isEmpty())
-        drain()
-        assertTrue(pagedList.isEmpty())
-        asyncDataSource.flush()
-        assertTrue(pagedList.isEmpty())
-        mBackgroundThread.executeAll()
-        assertTrue(pagedList.isEmpty())
-        verifyZeroInteractions(callback)
-
-        // Data source defers callbacks until flush, which posts result to main thread
-        mMainThread.executeAll()
-        assertFalse(pagedList.isEmpty())
-        // callback onInsert called once with initial size
-        verify(callback).onInserted(0, pagedList.size)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun addWeakCallbackEmpty() {
-        // Note: ignores Parameterized param
-        val asyncDataSource = AsyncListDataSource(ITEMS)
-        val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
-        val pagedList = ContiguousPagedList(
-                dataSource, mMainThread, mBackgroundThread, null,
-                PagedList.Config.Builder().setPageSize(10).build(), null,
-                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-        val callback = mock(PagedList.Callback::class.java)
-
-        // capture empty snapshot
-        val emptySnapshot = pagedList.snapshot()
-        assertTrue(pagedList.isEmpty())
-        assertTrue(emptySnapshot.isEmpty())
-
-        // verify that adding callback notifies nothing going from empty -> empty
-        pagedList.addWeakCallback(emptySnapshot, callback)
-        verifyZeroInteractions(callback)
-        pagedList.removeWeakCallback(callback)
-
-        // data added in asynchronously
-        asyncDataSource.flush()
-        drain()
-        assertFalse(pagedList.isEmpty())
-
-        // verify that adding callback notifies insert going from empty -> content
-        pagedList.addWeakCallback(emptySnapshot, callback)
-        verify(callback).onInserted(0, pagedList.size)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun boundaryCallback_empty() {
-        @Suppress("UNCHECKED_CAST")
-        val boundaryCallback =
-                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
-        val pagedList = createCountedPagedList(0,
-                listData = ArrayList(), boundaryCallback = boundaryCallback)
-        assertEquals(0, pagedList.size)
-
-        // nothing yet
-        verifyNoMoreInteractions(boundaryCallback)
-
-        // onZeroItemsLoaded posted, since creation often happens on BG thread
-        drain()
-        verify(boundaryCallback).onZeroItemsLoaded()
-        verifyNoMoreInteractions(boundaryCallback)
-    }
-
-    @Test
-    fun boundaryCallback_singleInitialLoad() {
-        val shortList = ITEMS.subList(0, 4)
-        @Suppress("UNCHECKED_CAST")
-        val boundaryCallback =
-                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
-        val pagedList = createCountedPagedList(0, listData = shortList,
-                initLoadSize = shortList.size, boundaryCallback = boundaryCallback)
-        assertEquals(shortList.size, pagedList.size)
-
-        // nothing yet
-        verifyNoMoreInteractions(boundaryCallback)
-
-        // onItemAtFrontLoaded / onItemAtEndLoaded posted, since creation often happens on BG thread
-        drain()
-        pagedList.loadAround(0)
-        drain()
-        verify(boundaryCallback).onItemAtFrontLoaded(shortList.first())
-        verify(boundaryCallback).onItemAtEndLoaded(shortList.last())
-        verifyNoMoreInteractions(boundaryCallback)
-    }
-
-    @Test
-    fun boundaryCallback_delayed() {
-        @Suppress("UNCHECKED_CAST")
-        val boundaryCallback =
-                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
-        val pagedList = createCountedPagedList(90,
-                initLoadSize = 20, prefetchDistance = 5, boundaryCallback = boundaryCallback)
-        verifyRange(80, 20, pagedList)
-
-        // nothing yet
-        verifyZeroInteractions(boundaryCallback)
-        drain()
-        verifyZeroInteractions(boundaryCallback)
-
-        // loading around last item causes onItemAtEndLoaded
-        pagedList.loadAround(if (mCounted) 99 else 19)
-        drain()
-        verifyRange(80, 20, pagedList)
-        verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
-        verifyNoMoreInteractions(boundaryCallback)
-
-        // prepending doesn't trigger callback...
-        pagedList.loadAround(if (mCounted) 80 else 0)
-        drain()
-        verifyRange(60, 40, pagedList)
-        verifyZeroInteractions(boundaryCallback)
-
-        // ...load rest of data, still no dispatch...
-        pagedList.loadAround(if (mCounted) 60 else 0)
-        drain()
-        pagedList.loadAround(if (mCounted) 40 else 0)
-        drain()
-        pagedList.loadAround(if (mCounted) 20 else 0)
-        drain()
-        verifyRange(0, 100, pagedList)
-        verifyZeroInteractions(boundaryCallback)
-
-        // ... finally try prepend, see 0 items, which will dispatch front callback
-        pagedList.loadAround(0)
-        drain()
-        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
-        verifyNoMoreInteractions(boundaryCallback)
-    }
-
-    private fun drain() {
-        var executed: Boolean
-        do {
-            executed = mBackgroundThread.executeAll()
-            executed = mMainThread.executeAll() || executed
-        } while (executed)
-    }
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "counted:{0}")
-        fun parameters(): Array<Array<Boolean>> {
-            return arrayOf(arrayOf(true), arrayOf(false))
-        }
-
-        private val ITEMS = List(100) { Item(it) }
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/Executors.kt b/paging/common/src/test/java/android/arch/paging/Executors.kt
deleted file mode 100644
index e1c45a7..0000000
--- a/paging/common/src/test/java/android/arch/paging/Executors.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import org.junit.Assert.fail
-import java.util.LinkedList
-import java.util.concurrent.Executor
-
-class TestExecutor : Executor {
-    private val mTasks = LinkedList<Runnable>()
-
-    override fun execute(runnable: Runnable) {
-        mTasks.add(runnable)
-    }
-
-    internal fun executeAll(): Boolean {
-        val consumed = !mTasks.isEmpty()
-
-        var task = mTasks.poll()
-        while (task != null) {
-            task.run()
-            task = mTasks.poll()
-        }
-        return consumed
-    }
-}
-
-class FailExecutor(val string: String = "Executor expected to be unused") : Executor {
-    override fun execute(runnable: Runnable?) {
-        fail(string)
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
deleted file mode 100644
index 060094a..0000000
--- a/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-
-@RunWith(JUnit4::class)
-class ItemKeyedDataSourceTest {
-
-    // ----- STANDARD -----
-
-    private fun loadInitial(dataSource: ItemDataSource, key: Key?, initialLoadSize: Int,
-            enablePlaceholders: Boolean): PageResult<Item> {
-        @Suppress("UNCHECKED_CAST")
-        val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Item>
-        @Suppress("UNCHECKED_CAST")
-        val captor = ArgumentCaptor.forClass(PageResult::class.java)
-                as ArgumentCaptor<PageResult<Item>>
-
-        dataSource.dispatchLoadInitial(key, initialLoadSize,
-                /* ignored pageSize */ 10, enablePlaceholders, FailExecutor(), receiver)
-
-        verify(receiver).onPageResult(anyInt(), captor.capture())
-        verifyNoMoreInteractions(receiver)
-        assertNotNull(captor.value)
-        return captor.value
-    }
-
-    @Test
-    fun loadInitial() {
-        val dataSource = ItemDataSource()
-        val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[49]), 10, true)
-
-        assertEquals(45, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
-        assertEquals(45, result.trailingNulls)
-    }
-
-    @Test
-    fun loadInitial_keyMatchesSingleItem() {
-        val dataSource = ItemDataSource(items = ITEMS_BY_NAME_ID.subList(0, 1))
-
-        // this is tricky, since load after and load before with the passed key will fail
-        val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[0]), 20, true)
-
-        assertEquals(0, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.page)
-        assertEquals(0, result.trailingNulls)
-    }
-
-    @Test
-    fun loadInitial_keyMatchesLastItem() {
-        val dataSource = ItemDataSource()
-
-        // tricky, because load after key is empty, so another load before and load after required
-        val key = dataSource.getKey(ITEMS_BY_NAME_ID.last())
-        val result = loadInitial(dataSource, key, 20, true)
-
-        assertEquals(90, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(90, 100), result.page)
-        assertEquals(0, result.trailingNulls)
-    }
-
-    @Test
-    fun loadInitial_nullKey() {
-        val dataSource = ItemDataSource()
-
-        // dispatchLoadInitial(null, count) == dispatchLoadInitial(count)
-        val result = loadInitial(dataSource, null, 10, true)
-
-        assertEquals(0, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
-        assertEquals(90, result.trailingNulls)
-    }
-
-    @Test
-    fun loadInitial_keyPastEndOfList() {
-        val dataSource = ItemDataSource()
-
-        // if key is past entire data set, should return last items in data set
-        val key = Key("fz", 0)
-        val result = loadInitial(dataSource, key, 10, true)
-
-        // NOTE: ideally we'd load 10 items here, but it adds complexity and unpredictability to
-        // do: load after was empty, so pass full size to load before, since this can incur larger
-        // loads than requested (see keyMatchesLastItem test)
-        assertEquals(95, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.page)
-        assertEquals(0, result.trailingNulls)
-    }
-
-    // ----- UNCOUNTED -----
-
-    @Test
-    fun loadInitial_disablePlaceholders() {
-        val dataSource = ItemDataSource()
-
-        // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
-        val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
-        val result = loadInitial(dataSource, key, 10, false)
-
-        assertEquals(0, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
-        assertEquals(0, result.trailingNulls)
-    }
-
-    @Test
-    fun loadInitial_uncounted() {
-        val dataSource = ItemDataSource(counted = false)
-
-        // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
-        val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
-        val result = loadInitial(dataSource, key, 10, true)
-
-        assertEquals(0, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
-        assertEquals(0, result.trailingNulls)
-    }
-
-    @Test
-    fun loadInitial_nullKey_uncounted() {
-        val dataSource = ItemDataSource(counted = false)
-
-        // dispatchLoadInitial(null, count) == dispatchLoadInitial(count)
-        val result = loadInitial(dataSource, null, 10, true)
-
-        assertEquals(0, result.leadingNulls)
-        assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
-        assertEquals(0, result.trailingNulls)
-    }
-
-    // ----- EMPTY -----
-
-    @Test
-    fun loadInitial_empty() {
-        val dataSource = ItemDataSource(items = ArrayList())
-
-        // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
-        val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
-        val result = loadInitial(dataSource, key, 10, true)
-
-        assertEquals(0, result.leadingNulls)
-        assertTrue(result.page.isEmpty())
-        assertEquals(0, result.trailingNulls)
-    }
-
-    @Test
-    fun loadInitial_nullKey_empty() {
-        val dataSource = ItemDataSource(items = ArrayList())
-        val result = loadInitial(dataSource, null, 10, true)
-
-        assertEquals(0, result.leadingNulls)
-        assertTrue(result.page.isEmpty())
-        assertEquals(0, result.trailingNulls)
-    }
-
-    // ----- Other behavior -----
-
-    @Test
-    fun loadBefore() {
-        val dataSource = ItemDataSource()
-        @Suppress("UNCHECKED_CAST")
-        val callback = mock(ItemKeyedDataSource.LoadCallback::class.java)
-                as ItemKeyedDataSource.LoadCallback<Item>
-
-        dataSource.loadBefore(
-                ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback)
-
-        @Suppress("UNCHECKED_CAST")
-        val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
-        verify(callback).onResult(argument.capture())
-        verifyNoMoreInteractions(callback)
-
-        val observed = argument.value
-
-        assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
-    }
-
-    internal data class Key(val name: String, val id: Int)
-
-    internal data class Item(
-            val name: String, val id: Int, val balance: Double, val address: String)
-
-    internal class ItemDataSource(private val counted: Boolean = true,
-                                  private val items: List<Item> = ITEMS_BY_NAME_ID)
-            : ItemKeyedDataSource<Key, Item>() {
-
-        override fun loadInitial(
-                params: LoadInitialParams<Key>,
-                callback: LoadInitialCallback<Item>) {
-            val key = params.requestedInitialKey ?: Key("", Integer.MAX_VALUE)
-            val start = Math.max(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
-            val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
-
-            if (params.placeholdersEnabled && counted) {
-                callback.onResult(items.subList(start, endExclusive), start, items.size)
-            } else {
-                callback.onResult(items.subList(start, endExclusive))
-            }
-        }
-
-        override fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Item>) {
-            val start = findFirstIndexAfter(params.key)
-            val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
-
-            callback.onResult(items.subList(start, endExclusive))
-        }
-
-        override fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Item>) {
-            val firstIndexBefore = findFirstIndexBefore(params.key)
-            val endExclusive = Math.max(0, firstIndexBefore + 1)
-            val start = Math.max(0, firstIndexBefore - params.requestedLoadSize + 1)
-
-            callback.onResult(items.subList(start, endExclusive))
-        }
-
-        override fun getKey(item: Item): Key {
-            return Key(item.name, item.id)
-        }
-
-        private fun findFirstIndexAfter(key: Key): Int {
-            return items.indices.firstOrNull {
-                KEY_COMPARATOR.compare(key, getKey(items[it])) < 0
-            } ?: items.size
-        }
-
-        private fun findFirstIndexBefore(key: Key): Int {
-            return items.indices.reversed().firstOrNull {
-                KEY_COMPARATOR.compare(key, getKey(items[it])) > 0
-            } ?: -1
-        }
-    }
-
-    private fun performLoadInitial(
-            invalidateDataSource: Boolean = false,
-            callbackInvoker: (callback: ItemKeyedDataSource.LoadInitialCallback<String>) -> Unit) {
-        val dataSource = object : ItemKeyedDataSource<String, String>() {
-            override fun getKey(item: String): String {
-                return ""
-            }
-
-            override fun loadInitial(
-                    params: LoadInitialParams<String>,
-                    callback: LoadInitialCallback<String>) {
-                if (invalidateDataSource) {
-                    // invalidate data source so it's invalid when onResult() called
-                    invalidate()
-                }
-                callbackInvoker(callback)
-            }
-
-            override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String>) {
-                fail("loadAfter not expected")
-            }
-
-            override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String>) {
-                fail("loadBefore not expected")
-            }
-        }
-
-        ContiguousPagedList<String, String>(
-                dataSource, FailExecutor(), FailExecutor(), null,
-                PagedList.Config.Builder()
-                        .setPageSize(10)
-                        .build(),
-                "",
-                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-    }
-
-    @Test
-    fun loadInitialCallbackSuccess() = performLoadInitial {
-        // LoadInitialCallback correct usage
-        it.onResult(listOf("a", "b"), 0, 2)
-    }
-
-    @Test
-    fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial {
-        // Keyed LoadInitialCallback *can* accept result that's not a multiple of page size
-        val elevenLetterList = List(11) { "" + 'a' + it }
-        it.onResult(elevenLetterList, 0, 12)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackListTooBig() = performLoadInitial {
-        // LoadInitialCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b", "c"), 0, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackPositionTooLarge() = performLoadInitial {
-        // LoadInitialCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b"), 1, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackPositionNegative() = performLoadInitial {
-        // LoadInitialCallback can't accept negative position
-        it.onResult(listOf("a", "b", "c"), -1, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
-        // LoadInitialCallback can't accept empty result unless data set is empty
-        it.onResult(emptyList(), 0, 2)
-    }
-
-    @Test
-    fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
-        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
-        it.onResult(emptyList(), 0, 1)
-    }
-
-    private abstract class WrapperDataSource<K, A, B>(private val source: ItemKeyedDataSource<K, A>)
-            : ItemKeyedDataSource<K, B>() {
-        private val invalidatedCallback = DataSource.InvalidatedCallback {
-            invalidate()
-            removeCallback()
-        }
-
-        init {
-            source.addInvalidatedCallback(invalidatedCallback)
-        }
-
-        private fun removeCallback() {
-            removeInvalidatedCallback(invalidatedCallback)
-        }
-
-        override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
-            source.loadInitial(params, object : LoadInitialCallback<A>() {
-                override fun onResult(data: List<A>, position: Int, totalCount: Int) {
-                    callback.onResult(convert(data), position, totalCount)
-                }
-
-                override fun onResult(data: MutableList<A>) {
-                    callback.onResult(convert(data))
-                }
-            })
-        }
-
-        override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
-            source.loadAfter(params, object : LoadCallback<A>() {
-                override fun onResult(data: MutableList<A>) {
-                    callback.onResult(convert(data))
-                }
-            })
-        }
-
-        override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
-            source.loadBefore(params, object : LoadCallback<A>() {
-                override fun onResult(data: MutableList<A>) {
-                    callback.onResult(convert(data))
-                }
-            })
-        }
-
-        protected abstract fun convert(source: List<A>): List<B>
-    }
-
-    private data class DecoratedItem(val item: Item)
-
-    private class DecoratedWrapperDataSource(private val source: ItemKeyedDataSource<Key, Item>)
-            : WrapperDataSource<Key, Item, DecoratedItem>(source) {
-        override fun convert(source: List<Item>): List<DecoratedItem> {
-            return source.map { DecoratedItem(it) }
-        }
-
-        override fun getKey(item: DecoratedItem): Key {
-            return source.getKey(item.item)
-        }
-    }
-
-    private fun verifyWrappedDataSource(createWrapper:
-            (ItemKeyedDataSource<Key, Item>) -> ItemKeyedDataSource<Key, DecoratedItem>) {
-        // verify that it's possible to wrap an ItemKeyedDataSource, and add info to its data
-
-        val orig = ItemDataSource(items = ITEMS_BY_NAME_ID)
-        val wrapper = DecoratedWrapperDataSource(orig)
-
-        // load initial
-        @Suppress("UNCHECKED_CAST")
-        val loadInitialCallback = mock(ItemKeyedDataSource.LoadInitialCallback::class.java)
-                as ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>
-        val initKey = orig.getKey(ITEMS_BY_NAME_ID.first())
-        wrapper.loadInitial(ItemKeyedDataSource.LoadInitialParams(initKey, 10, false),
-                loadInitialCallback)
-        verify(loadInitialCallback).onResult(
-                ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
-        verifyNoMoreInteractions(loadInitialCallback)
-
-        @Suppress("UNCHECKED_CAST")
-        val loadCallback = mock(ItemKeyedDataSource.LoadCallback::class.java)
-                as ItemKeyedDataSource.LoadCallback<DecoratedItem>
-        val key = orig.getKey(ITEMS_BY_NAME_ID[20])
-        // load after
-        wrapper.loadAfter(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
-        verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(21, 31).map { DecoratedItem(it) })
-        verifyNoMoreInteractions(loadCallback)
-
-        // load before
-        wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
-        verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(10, 20).map { DecoratedItem(it) })
-        verifyNoMoreInteractions(loadCallback)
-    }
-
-    @Test
-    fun testManualWrappedDataSource() = verifyWrappedDataSource {
-        DecoratedWrapperDataSource(it)
-    }
-
-    @Test
-    fun testListConverterWrappedDataSource() = verifyWrappedDataSource {
-        it.mapByPage { it.map { DecoratedItem(it) } }
-    }
-
-    @Test
-    fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
-        it.map { DecoratedItem(it) }
-    }
-
-    companion object {
-        private val ITEM_COMPARATOR = compareBy<Item>({ it.name }).thenByDescending({ it.id })
-        private val KEY_COMPARATOR = compareBy<Key>({ it.name }).thenByDescending({ it.id })
-
-        private val ITEMS_BY_NAME_ID = List(100) {
-            val names = Array(10) { "f" + ('a' + it) }
-            Item(names[it % 10],
-                    it,
-                    Math.random() * 1000,
-                    (Math.random() * 200).toInt().toString() + " fake st.")
-        }.sortedWith(ITEM_COMPARATOR)
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt
deleted file mode 100644
index f7ea4b6..0000000
--- a/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright 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.paging
-
-import org.junit.Assert.assertEquals
-import org.junit.Assert.fail
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-
-@RunWith(JUnit4::class)
-class PageKeyedDataSourceTest {
-    private val mMainThread = TestExecutor()
-    private val mBackgroundThread = TestExecutor()
-
-    internal data class Item(val name: String)
-
-    internal data class Page(val prev: String?, val data: List<Item>, val next: String?)
-
-    internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP)
-            : PageKeyedDataSource<String, Item>() {
-
-        private fun getPage(key: String): Page = data[key]!!
-
-        override fun loadInitial(
-                params: LoadInitialParams<String>,
-                callback: LoadInitialCallback<String, Item>) {
-            val page = getPage(INIT_KEY)
-            callback.onResult(page.data, page.prev, page.next)
-        }
-
-        override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
-            val page = getPage(params.key)
-            callback.onResult(page.data, page.prev)
-        }
-
-        override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
-            val page = getPage(params.key)
-            callback.onResult(page.data, page.next)
-        }
-    }
-
-    @Test
-    fun loadFullVerify() {
-        // validate paging entire ItemDataSource results in full, correctly ordered data
-        val pagedList = ContiguousPagedList<String, Item>(ItemDataSource(),
-                mMainThread, mBackgroundThread,
-                null, PagedList.Config.Builder().setPageSize(100).build(), null,
-                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-
-        // validate initial load
-        assertEquals(PAGE_MAP[INIT_KEY]!!.data, pagedList)
-
-        // flush the remaining loads
-        for (i in 0..PAGE_MAP.keys.size) {
-            pagedList.loadAround(0)
-            pagedList.loadAround(pagedList.size - 1)
-            drain()
-        }
-
-        // validate full load
-        assertEquals(ITEM_LIST, pagedList)
-    }
-
-    private fun performLoadInitial(invalidateDataSource: Boolean = false,
-            callbackInvoker:
-                    (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit) {
-        val dataSource = object : PageKeyedDataSource<String, String>() {
-            override fun loadInitial(
-                    params: LoadInitialParams<String>,
-                    callback: LoadInitialCallback<String, String>) {
-                if (invalidateDataSource) {
-                    // invalidate data source so it's invalid when onResult() called
-                    invalidate()
-                }
-                callbackInvoker(callback)
-            }
-
-            override fun loadBefore(
-                    params: LoadParams<String>,
-                    callback: LoadCallback<String, String>) {
-                fail("loadBefore not expected")
-            }
-
-            override fun loadAfter(
-                    params: LoadParams<String>,
-                    callback: LoadCallback<String, String>) {
-                fail("loadAfter not expected")
-            }
-        }
-
-        ContiguousPagedList<String, String>(
-                dataSource, FailExecutor(), FailExecutor(), null,
-                PagedList.Config.Builder()
-                        .setPageSize(10)
-                        .build(),
-                "",
-                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-    }
-
-    @Test
-    fun loadInitialCallbackSuccess() = performLoadInitial {
-        // LoadInitialCallback correct usage
-        it.onResult(listOf("a", "b"), 0, 2, null, null)
-    }
-
-    @Test
-    fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial {
-        // Keyed LoadInitialCallback *can* accept result that's not a multiple of page size
-        val elevenLetterList = List(11) { "" + 'a' + it }
-        it.onResult(elevenLetterList, 0, 12, null, null)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackListTooBig() = performLoadInitial {
-        // LoadInitialCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b", "c"), 0, 2, null, null)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackPositionTooLarge() = performLoadInitial {
-        // LoadInitialCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b"), 1, 2, null, null)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackPositionNegative() = performLoadInitial {
-        // LoadInitialCallback can't accept negative position
-        it.onResult(listOf("a", "b", "c"), -1, 2, null, null)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
-        // LoadInitialCallback can't accept empty result unless data set is empty
-        it.onResult(emptyList(), 0, 2, null, null)
-    }
-
-    @Test
-    fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
-        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
-        it.onResult(emptyList(), 0, 1, null, null)
-    }
-
-    private abstract class WrapperDataSource<K, A, B>(private val source: PageKeyedDataSource<K, A>)
-            : PageKeyedDataSource<K, B>() {
-        private val invalidatedCallback = DataSource.InvalidatedCallback {
-            invalidate()
-            removeCallback()
-        }
-
-        init {
-            source.addInvalidatedCallback(invalidatedCallback)
-        }
-
-        private fun removeCallback() {
-            removeInvalidatedCallback(invalidatedCallback)
-        }
-
-        override fun loadInitial(params: LoadInitialParams<K>,
-                callback: LoadInitialCallback<K, B>) {
-            source.loadInitial(params, object : LoadInitialCallback<K, A>() {
-                override fun onResult(data: List<A>, position: Int, totalCount: Int,
-                        previousPageKey: K?, nextPageKey: K?) {
-                    callback.onResult(convert(data), position, totalCount,
-                            previousPageKey, nextPageKey)
-                }
-
-                override fun onResult(data: MutableList<A>, previousPageKey: K?, nextPageKey: K?) {
-                    callback.onResult(convert(data), previousPageKey, nextPageKey)
-                }
-            })
-        }
-
-        override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<K, B>) {
-            source.loadBefore(params, object : LoadCallback<K, A>() {
-                override fun onResult(data: List<A>, adjacentPageKey: K?) {
-                    callback.onResult(convert(data), adjacentPageKey)
-                }
-            })
-        }
-
-        override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<K, B>) {
-            source.loadAfter(params, object : LoadCallback<K, A>() {
-                override fun onResult(data: List<A>, adjacentPageKey: K?) {
-                    callback.onResult(convert(data), adjacentPageKey)
-                }
-            })
-        }
-
-        protected abstract fun convert(source: List<A>): List<B>
-    }
-
-    private class StringWrapperDataSource<K, V>(source: PageKeyedDataSource<K, V>)
-            : WrapperDataSource<K, V, String>(source) {
-        override fun convert(source: List<V>): List<String> {
-            return source.map { it.toString() }
-        }
-    }
-
-    private fun verifyWrappedDataSource(createWrapper:
-            (PageKeyedDataSource<String, Item>) -> PageKeyedDataSource<String, String>) {
-        // verify that it's possible to wrap a PageKeyedDataSource, and add info to its data
-        val orig = ItemDataSource(data = PAGE_MAP)
-        val wrapper = createWrapper(orig)
-
-        // load initial
-        @Suppress("UNCHECKED_CAST")
-        val loadInitialCallback = mock(PageKeyedDataSource.LoadInitialCallback::class.java)
-                as PageKeyedDataSource.LoadInitialCallback<String, String>
-
-        wrapper.loadInitial(PageKeyedDataSource.LoadInitialParams<String>(4, true),
-                loadInitialCallback)
-        val expectedInitial = PAGE_MAP.get(INIT_KEY)!!
-        verify(loadInitialCallback).onResult(expectedInitial.data.map { it.toString() },
-                expectedInitial.prev, expectedInitial.next)
-        verifyNoMoreInteractions(loadInitialCallback)
-
-        @Suppress("UNCHECKED_CAST")
-        val loadCallback = mock(PageKeyedDataSource.LoadCallback::class.java)
-                as PageKeyedDataSource.LoadCallback<String, String>
-        // load after
-        wrapper.loadAfter(PageKeyedDataSource.LoadParams(expectedInitial.next!!, 4), loadCallback)
-        val expectedAfter = PAGE_MAP.get(expectedInitial.next)!!
-        verify(loadCallback).onResult(expectedAfter.data.map { it.toString() },
-                expectedAfter.next)
-        verifyNoMoreInteractions(loadCallback)
-
-        // load before
-        wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev!!, 4), loadCallback)
-        verify(loadCallback).onResult(expectedInitial.data.map { it.toString() },
-                expectedInitial.prev)
-        verifyNoMoreInteractions(loadCallback)
-    }
-
-    @Test
-    fun testManualWrappedDataSource() = verifyWrappedDataSource {
-        StringWrapperDataSource(it)
-    }
-
-    @Test
-    fun testListConverterWrappedDataSource() = verifyWrappedDataSource {
-        it.mapByPage { it.map { it.toString() } }
-    }
-    @Test
-    fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
-        it.map { it.toString() }
-    }
-
-    companion object {
-        // first load is 2nd page to ensure we test prepend as well as append behavior
-        private val INIT_KEY: String = "key 2"
-        private val PAGE_MAP: Map<String, Page>
-        private val ITEM_LIST: List<Item>
-
-        init {
-            val map = HashMap<String, Page>()
-            val list = ArrayList<Item>()
-            val pageCount = 5
-            for (i in 1..pageCount) {
-                val data = List(4) { Item("name $i $it") }
-                list.addAll(data)
-
-                val key = "key $i"
-                val prev = if (i > 1) ("key " + (i - 1)) else null
-                val next = if (i < pageCount) ("key " + (i + 1)) else null
-                map.put(key, Page(prev, data, next))
-            }
-            PAGE_MAP = map
-            ITEM_LIST = list
-        }
-    }
-
-    private fun drain() {
-        var executed: Boolean
-        do {
-            executed = mBackgroundThread.executeAll()
-            executed = mMainThread.executeAll() || executed
-        } while (executed)
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/PagedStorageTest.kt b/paging/common/src/test/java/android/arch/paging/PagedStorageTest.kt
deleted file mode 100644
index 9b53f62..0000000
--- a/paging/common/src/test/java/android/arch/paging/PagedStorageTest.kt
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import org.junit.Assert.assertArrayEquals
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-
-@RunWith(JUnit4::class)
-class PagedStorageTest {
-    private fun createPage(vararg strings: String): List<String> {
-        return strings.asList()
-    }
-
-    @Test
-    fun construct() {
-        val storage = PagedStorage(2, createPage("a", "b"), 2)
-
-        assertArrayEquals(arrayOf(null, null, "a", "b", null, null), storage.toArray())
-        assertEquals(6, storage.size)
-    }
-
-    @Test
-    fun appendFill() {
-        val callback = mock(PagedStorage.Callback::class.java)
-
-        val storage = PagedStorage(2, createPage("a", "b"), 2)
-        storage.appendPage(createPage("c", "d"), callback)
-
-        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d"), storage.toArray())
-        verify(callback).onPageAppended(4, 2, 0)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun appendAdd() {
-        val callback = mock(PagedStorage.Callback::class.java)
-
-        val storage = PagedStorage(2, createPage("a", "b"), 0)
-        storage.appendPage(createPage("c", "d"), callback)
-
-        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d"), storage.toArray())
-        verify(callback).onPageAppended(4, 0, 2)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun appendFillAdd() {
-        val callback = mock(PagedStorage.Callback::class.java)
-
-        val storage = PagedStorage(2, createPage("a", "b"), 2)
-
-        // change 2 nulls into c, d
-        storage.appendPage(createPage("c", "d"), callback)
-
-        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d"), storage.toArray())
-        verify(callback).onPageAppended(4, 2, 0)
-        verifyNoMoreInteractions(callback)
-
-        // append e, f
-        storage.appendPage(createPage("e", "f"), callback)
-
-        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d", "e", "f"), storage.toArray())
-        verify(callback).onPageAppended(6, 0, 2)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun prependFill() {
-        val callback = mock(PagedStorage.Callback::class.java)
-
-        val storage = PagedStorage(2, createPage("c", "d"), 2)
-        storage.prependPage(createPage("a", "b"), callback)
-
-        assertArrayEquals(arrayOf("a", "b", "c", "d", null, null), storage.toArray())
-        verify(callback).onPagePrepended(0, 2, 0)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun prependAdd() {
-        val callback = mock(PagedStorage.Callback::class.java)
-
-        val storage = PagedStorage(0, createPage("c", "d"), 2)
-        storage.prependPage(createPage("a", "b"), callback)
-
-        assertArrayEquals(arrayOf("a", "b", "c", "d", null, null), storage.toArray())
-        verify(callback).onPagePrepended(0, 0, 2)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun prependFillAdd() {
-        val callback = mock(PagedStorage.Callback::class.java)
-
-        val storage = PagedStorage(2, createPage("e", "f"), 2)
-
-        // change 2 nulls into c, d
-        storage.prependPage(createPage("c", "d"), callback)
-
-        assertArrayEquals(arrayOf("c", "d", "e", "f", null, null), storage.toArray())
-        verify(callback).onPagePrepended(0, 2, 0)
-        verifyNoMoreInteractions(callback)
-
-        // prepend a, b
-        storage.prependPage(createPage("a", "b"), callback)
-
-        assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", null, null), storage.toArray())
-        verify(callback).onPagePrepended(0, 0, 2)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun isTiled_addend_smallerPageIsNotLast() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage(0, createPage("a", "a"), 0)
-        assertTrue(storage.isTiled)
-
-        storage.appendPage(createPage("a", "a"), callback)
-        assertTrue(storage.isTiled)
-
-        storage.appendPage(createPage("a"), callback)
-        assertTrue(storage.isTiled)
-
-        // no matter what we append here, we're no longer tiled
-        storage.appendPage(createPage("a", "a"), callback)
-        assertFalse(storage.isTiled)
-    }
-
-    @Test
-    fun isTiled_append_growingSizeDisable() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage(0, createPage("a", "a"), 0)
-        assertTrue(storage.isTiled)
-
-        // page size can't grow from append
-        storage.appendPage(createPage("a", "a", "a"), callback)
-        assertFalse(storage.isTiled)
-    }
-
-    @Test
-    fun isTiled_prepend_smallerPage() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage(0, createPage("a"), 0)
-        assertTrue(storage.isTiled)
-
-        storage.prependPage(createPage("a", "a"), callback)
-        assertTrue(storage.isTiled)
-
-        storage.prependPage(createPage("a", "a"), callback)
-        assertTrue(storage.isTiled)
-
-        storage.prependPage(createPage("a"), callback)
-        assertFalse(storage.isTiled)
-    }
-
-    @Test
-    fun isTiled_prepend_smallerThanInitialPage() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage(0, createPage("a", "a"), 0)
-        assertTrue(storage.isTiled)
-
-        storage.prependPage(createPage("a"), callback)
-        assertFalse(storage.isTiled)
-    }
-
-    @Test
-    fun get_tiled() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage(1, createPage("a", "b"), 5)
-        assertTrue(storage.isTiled)
-
-        storage.appendPage(createPage("c", "d"), callback)
-        storage.appendPage(createPage("e", "f"), callback)
-
-        assertTrue(storage.isTiled)
-        assertArrayEquals(arrayOf(null, "a", "b", "c", "d", "e", "f", null), storage.toArray())
-    }
-
-    @Test
-    fun get_nonTiled() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage(1, createPage("a"), 6)
-        assertTrue(storage.isTiled)
-
-        storage.appendPage(createPage("b", "c"), callback)
-        storage.appendPage(createPage("d", "e", "f"), callback)
-
-        assertFalse(storage.isTiled)
-        assertArrayEquals(arrayOf(null, "a", "b", "c", "d", "e", "f", null), storage.toArray())
-    }
-
-    @Test
-    fun insertOne() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(2, createPage("c", "d"), 3, 0, callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
-        verify(callback).onInitialized(7)
-        verifyNoMoreInteractions(callback)
-
-        storage.insertPage(4, createPage("e", "f"), callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf(null, null, "c", "d", "e", "f", null), storage.toArray())
-        verify(callback).onPageInserted(4, 2)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun insertThree() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(2, createPage("c", "d"), 3, 0, callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
-        verify(callback).onInitialized(7)
-        verifyNoMoreInteractions(callback)
-
-        // first, insert 1st page
-        storage.insertPage(0, createPage("a", "b"), callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf("a", "b", "c", "d", null, null, null), storage.toArray())
-        verify(callback).onPageInserted(0, 2)
-        verifyNoMoreInteractions(callback)
-
-        // then 3rd page
-        storage.insertPage(4, createPage("e", "f"), callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", null), storage.toArray())
-        verify(callback).onPageInserted(4, 2)
-        verifyNoMoreInteractions(callback)
-
-        // then last, small page
-        storage.insertPage(6, createPage("g"), callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", "g"), storage.toArray())
-        verify(callback).onPageInserted(6, 1)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun insertLastFirst() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(6, createPage("g"), 0, 0, callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf(null, null, null, null, null, null, "g"), storage.toArray())
-        verify(callback).onInitialized(7)
-        verifyNoMoreInteractions(callback)
-
-        // insert 1st page
-        storage.insertPage(0, createPage("a", "b"), callback)
-
-        assertEquals(7, storage.size)
-        assertArrayEquals(arrayOf("a", "b", null, null, null, null, "g"), storage.toArray())
-        verify(callback).onPageInserted(0, 2)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun insertFailure_decreaseLast() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(2, createPage("c", "d"), 0, 0, callback)
-
-        // should throw, page too small
-        storage.insertPage(0, createPage("a"), callback)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun insertFailure_increase() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(0, createPage("a", "b"), 3, 0, callback)
-
-        // should throw, page too big
-        storage.insertPage(2, createPage("c", "d", "e"), callback)
-    }
-
-    @Test
-    fun allocatePlaceholders_simple() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(2, createPage("c"), 2, 0, callback)
-
-        verify(callback).onInitialized(5)
-
-        storage.allocatePlaceholders(2, 1, 1, callback)
-
-        verify(callback).onPagePlaceholderInserted(1)
-        verify(callback).onPagePlaceholderInserted(3)
-        verifyNoMoreInteractions(callback)
-
-        assertArrayEquals(arrayOf(null, null, "c", null, null), storage.toArray())
-    }
-
-    @Test
-    fun allocatePlaceholders_adoptPageSize() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(4, createPage("e"), 0, 0, callback)
-
-        verify(callback).onInitialized(5)
-
-        storage.allocatePlaceholders(0, 2, 2, callback)
-
-        verify(callback).onPagePlaceholderInserted(0)
-        verify(callback).onPagePlaceholderInserted(1)
-        verifyNoMoreInteractions(callback)
-
-        assertArrayEquals(arrayOf(null, null, null, null, "e"), storage.toArray())
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun allocatePlaceholders_cannotShrinkPageSize() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(4, createPage("e", "f"), 0, 0, callback)
-
-        verify(callback).onInitialized(6)
-
-        storage.allocatePlaceholders(0, 2, 1, callback)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun allocatePlaceholders_cannotAdoptPageSize() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(2, createPage("c", "d"), 2, 0, callback)
-
-        verify(callback).onInitialized(6)
-
-        storage.allocatePlaceholders(0, 2, 3, callback)
-    }
-
-    @Test
-    fun get_placeholdersMulti() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(2, createPage("c", "d"), 3, 0, callback)
-
-        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
-
-        storage.allocatePlaceholders(0, 10, 2, callback)
-
-        // allocating placeholders shouldn't affect result of get
-        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
-    }
-
-    @Test
-    fun hasPage() {
-        val callback = mock(PagedStorage.Callback::class.java)
-        val storage = PagedStorage<String>()
-
-        storage.init(4, createPage("e"), 0, 0, callback)
-
-        assertFalse(storage.hasPage(1, 0))
-        assertFalse(storage.hasPage(1, 1))
-        assertFalse(storage.hasPage(1, 2))
-        assertFalse(storage.hasPage(1, 3))
-        assertTrue(storage.hasPage(1, 4))
-
-        assertFalse(storage.hasPage(2, 0))
-        assertFalse(storage.hasPage(2, 1))
-        assertTrue(storage.hasPage(2, 2))
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
deleted file mode 100644
index eab778c..0000000
--- a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright 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.paging
-
-import org.junit.Assert.assertEquals
-import org.junit.Assert.fail
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-
-@RunWith(JUnit4::class)
-class PositionalDataSourceTest {
-    private fun computeInitialLoadPos(
-            requestedStartPosition: Int,
-            requestedLoadSize: Int,
-            pageSize: Int,
-            totalCount: Int): Int {
-        val params = PositionalDataSource.LoadInitialParams(
-                requestedStartPosition, requestedLoadSize, pageSize, true)
-        return PositionalDataSource.computeInitialLoadPosition(params, totalCount)
-    }
-
-    @Test
-    fun computeInitialLoadPositionZero() {
-        assertEquals(0, computeInitialLoadPos(
-                requestedStartPosition = 0,
-                requestedLoadSize = 30,
-                pageSize = 10,
-                totalCount = 100))
-    }
-
-    @Test
-    fun computeInitialLoadPositionRequestedPositionIncluded() {
-        assertEquals(10, computeInitialLoadPos(
-                requestedStartPosition = 10,
-                requestedLoadSize = 10,
-                pageSize = 10,
-                totalCount = 100))
-    }
-
-    @Test
-    fun computeInitialLoadPositionRound() {
-        assertEquals(10, computeInitialLoadPos(
-                requestedStartPosition = 13,
-                requestedLoadSize = 30,
-                pageSize = 10,
-                totalCount = 100))
-    }
-
-    @Test
-    fun computeInitialLoadPositionEndAdjusted() {
-        assertEquals(70, computeInitialLoadPos(
-                requestedStartPosition = 99,
-                requestedLoadSize = 30,
-                pageSize = 10,
-                totalCount = 100))
-    }
-
-    @Test
-    fun computeInitialLoadPositionEndAdjustedAndAligned() {
-        assertEquals(70, computeInitialLoadPos(
-                requestedStartPosition = 99,
-                requestedLoadSize = 35,
-                pageSize = 10,
-                totalCount = 100))
-    }
-
-    @Test
-    fun fullLoadWrappedAsContiguous() {
-        // verify that prepend / append work correctly with a PositionalDataSource, made contiguous
-        val config = PagedList.Config.Builder()
-                .setPageSize(10)
-                .setInitialLoadSizeHint(10)
-                .setEnablePlaceholders(true)
-                .build()
-        val dataSource: PositionalDataSource<Int> = ListDataSource((0..99).toList())
-        val testExecutor = TestExecutor()
-        val pagedList = ContiguousPagedList(dataSource.wrapAsContiguousWithoutPlaceholders(),
-                testExecutor, testExecutor, null, config, 15,
-                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-
-        assertEquals((10..19).toList(), pagedList)
-
-        // prepend + append work correctly
-        pagedList.loadAround(5)
-        testExecutor.executeAll()
-        assertEquals((0..29).toList(), pagedList)
-
-        // and load the rest of the data to be sure further appends work
-        for (i in (2..9)) {
-            pagedList.loadAround(i * 10 - 5)
-            testExecutor.executeAll()
-            assertEquals((0..i * 10 + 9).toList(), pagedList)
-        }
-    }
-
-    private fun performLoadInitial(
-            enablePlaceholders: Boolean = true,
-            invalidateDataSource: Boolean = false,
-            callbackInvoker: (callback: PositionalDataSource.LoadInitialCallback<String>) -> Unit) {
-        val dataSource = object : PositionalDataSource<String>() {
-            override fun loadInitial(
-                    params: LoadInitialParams,
-                    callback: LoadInitialCallback<String>) {
-                if (invalidateDataSource) {
-                    // invalidate data source so it's invalid when onResult() called
-                    invalidate()
-                }
-                callbackInvoker(callback)
-            }
-
-            override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
-                fail("loadRange not expected")
-            }
-        }
-
-        val config = PagedList.Config.Builder()
-                .setPageSize(10)
-                .setEnablePlaceholders(enablePlaceholders)
-                .build()
-        if (enablePlaceholders) {
-            TiledPagedList(dataSource, FailExecutor(), FailExecutor(), null, config, 0)
-        } else {
-            ContiguousPagedList(dataSource.wrapAsContiguousWithoutPlaceholders(),
-                    FailExecutor(), FailExecutor(), null, config, null,
-                    ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-        }
-    }
-
-    @Test
-    fun initialLoadCallbackSuccess() = performLoadInitial {
-        // LoadInitialCallback correct usage
-        it.onResult(listOf("a", "b"), 0, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackNotPageSizeMultiple() = performLoadInitial {
-        // Positional LoadInitialCallback can't accept result that's not a multiple of page size
-        val elevenLetterList = List(11) { "" + 'a' + it }
-        it.onResult(elevenLetterList, 0, 12)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackListTooBig() = performLoadInitial {
-        // LoadInitialCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b", "c"), 0, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackPositionTooLarge() = performLoadInitial {
-        // LoadInitialCallback can't accept pos + list > totalCount
-        it.onResult(listOf("a", "b"), 1, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackPositionNegative() = performLoadInitial {
-        // LoadInitialCallback can't accept negative position
-        it.onResult(listOf("a", "b", "c"), -1, 2)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
-        // LoadInitialCallback can't accept empty result unless data set is empty
-        it.onResult(emptyList(), 0, 2)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun initialLoadCallbackRequireTotalCount() = performLoadInitial(enablePlaceholders = true) {
-        // LoadInitialCallback requires 3 args when placeholders enabled
-        it.onResult(listOf("a", "b"), 0)
-    }
-
-    @Test
-    fun initialLoadCallbackSuccessTwoArg() = performLoadInitial(enablePlaceholders = false) {
-        // LoadInitialCallback correct 2 arg usage
-        it.onResult(listOf("a", "b"), 0)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackPosNegativeTwoArg() = performLoadInitial(enablePlaceholders = false) {
-        // LoadInitialCallback can't accept negative position
-        it.onResult(listOf("a", "b"), -1)
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun initialLoadCallbackEmptyWithOffset() = performLoadInitial(enablePlaceholders = false) {
-        // LoadInitialCallback can't accept empty result unless pos is 0
-        it.onResult(emptyList(), 1)
-    }
-
-    @Test
-    fun initialLoadCallbackInvalidTwoArg() = performLoadInitial(invalidateDataSource = true) {
-        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
-        it.onResult(emptyList(), 1)
-    }
-
-    @Test
-    fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
-        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
-        it.onResult(emptyList(), 0, 1)
-    }
-
-    private abstract class WrapperDataSource<in A, B>(private val source: PositionalDataSource<A>)
-            : PositionalDataSource<B>() {
-        private val invalidatedCallback = DataSource.InvalidatedCallback {
-            invalidate()
-            removeCallback()
-        }
-
-        init {
-            source.addInvalidatedCallback(invalidatedCallback)
-        }
-
-        private fun removeCallback() {
-            removeInvalidatedCallback(invalidatedCallback)
-        }
-
-        override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<B>) {
-            source.loadInitial(params, object : LoadInitialCallback<A>() {
-                override fun onResult(data: List<A>, position: Int, totalCount: Int) {
-                    callback.onResult(convert(data), position, totalCount)
-                }
-
-                override fun onResult(data: List<A>, position: Int) {
-                    callback.onResult(convert(data), position)
-                }
-            })
-        }
-
-        override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<B>) {
-            source.loadRange(params, object : LoadRangeCallback<A>() {
-                override fun onResult(data: List<A>) {
-                    callback.onResult(convert(data))
-                }
-            })
-        }
-
-        protected abstract fun convert(source: List<A>): List<B>
-    }
-
-    private class StringWrapperDataSource<in A>(source: PositionalDataSource<A>)
-            : WrapperDataSource<A, String>(source) {
-        override fun convert(source: List<A>): List<String> {
-            return source.map { it.toString() }
-        }
-    }
-
-    private fun verifyWrappedDataSource(
-            createWrapper: (PositionalDataSource<Int>) -> PositionalDataSource<String>) {
-        val orig = ListDataSource(listOf(0, 5, 4, 8, 12))
-        val wrapper = createWrapper(orig)
-
-        // load initial
-        @Suppress("UNCHECKED_CAST")
-        val loadInitialCallback = mock(PositionalDataSource.LoadInitialCallback::class.java)
-                as PositionalDataSource.LoadInitialCallback<String>
-
-        wrapper.loadInitial(PositionalDataSource.LoadInitialParams(0, 2, 1, true),
-                loadInitialCallback)
-        verify(loadInitialCallback).onResult(listOf("0", "5"), 0, 5)
-        verifyNoMoreInteractions(loadInitialCallback)
-
-        // load range
-        @Suppress("UNCHECKED_CAST")
-        val loadRangeCallback = mock(PositionalDataSource.LoadRangeCallback::class.java)
-                as PositionalDataSource.LoadRangeCallback<String>
-
-        wrapper.loadRange(PositionalDataSource.LoadRangeParams(2, 3), loadRangeCallback)
-        verify(loadRangeCallback).onResult(listOf("4", "8", "12"))
-        verifyNoMoreInteractions(loadRangeCallback)
-
-        // check invalidation behavior
-        val invalCallback = mock(DataSource.InvalidatedCallback::class.java)
-        wrapper.addInvalidatedCallback(invalCallback)
-        orig.invalidate()
-        verify(invalCallback).onInvalidated()
-        verifyNoMoreInteractions(invalCallback)
-    }
-
-    @Test
-    fun testManualWrappedDataSource() = verifyWrappedDataSource {
-        StringWrapperDataSource(it)
-    }
-
-    @Test
-    fun testListConverterWrappedDataSource() = verifyWrappedDataSource {
-        it.mapByPage { it.map { it.toString() } }
-    }
-
-    @Test
-    fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
-        it.map { it.toString() }
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
deleted file mode 100644
index f41e6a6..0000000
--- a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 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.paging
-
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import java.util.Collections
-
-@Suppress("DEPRECATION")
-@RunWith(JUnit4::class)
-class TiledDataSourceTest {
-
-    fun TiledDataSource<String>.loadInitial(
-            startPosition: Int, count: Int, pageSize: Int): List<String> {
-        @Suppress("UNCHECKED_CAST")
-        val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<String>
-
-        this.dispatchLoadInitial(true, startPosition, count, pageSize, FailExecutor(), receiver)
-
-        @Suppress("UNCHECKED_CAST")
-        val argument = ArgumentCaptor.forClass(PageResult::class.java)
-                as ArgumentCaptor<PageResult<String>>
-        verify(receiver).onPageResult(eq(PageResult.INIT), argument.capture())
-        verifyNoMoreInteractions(receiver)
-
-        val observed = argument.value
-
-        return observed.page
-    }
-
-    @Test
-    fun loadInitialEmpty() {
-        class EmptyDataSource : TiledDataSource<String>() {
-            override fun countItems(): Int {
-                return 0
-            }
-
-            override fun loadRange(startPosition: Int, count: Int): List<String> {
-                return emptyList()
-            }
-        }
-
-        assertEquals(Collections.EMPTY_LIST, EmptyDataSource().loadInitial(0, 1, 5))
-    }
-
-    @Test
-    fun loadInitialTooLong() {
-        val list = List(26) { "" + 'a' + it }
-        class AlphabetDataSource : TiledDataSource<String>() {
-            override fun countItems(): Int {
-                return list.size
-            }
-
-            override fun loadRange(startPosition: Int, count: Int): List<String> {
-                return list.subList(startPosition, startPosition + count)
-            }
-        }
-        // baseline behavior
-        assertEquals(list, AlphabetDataSource().loadInitial(0, 26, 10))
-        assertEquals(list, AlphabetDataSource().loadInitial(50, 26, 10))
-    }
-}
diff --git a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
deleted file mode 100644
index fbd8f54..0000000
--- a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertSame
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
-
-@RunWith(JUnit4::class)
-class TiledPagedListTest {
-    private val mMainThread = TestExecutor()
-    private val mBackgroundThread = TestExecutor()
-
-    private class Item(position: Int) {
-        val name: String = "Item $position"
-
-        override fun toString(): String {
-            return name
-        }
-    }
-
-    private fun verifyLoadedPages(list: List<Item>, vararg loadedPages: Int) {
-        val loadedPageList = loadedPages.asList()
-        assertEquals(ITEMS.size, list.size)
-        for (i in list.indices) {
-            if (loadedPageList.contains(i / PAGE_SIZE)) {
-                assertSame("Index $i", ITEMS[i], list[i])
-            } else {
-                assertNull("Index $i", list[i])
-            }
-        }
-    }
-
-    private fun createTiledPagedList(loadPosition: Int, initPageCount: Int,
-            prefetchDistance: Int = PAGE_SIZE,
-            listData: List<Item> = ITEMS,
-            boundaryCallback: PagedList.BoundaryCallback<Item>? = null): TiledPagedList<Item> {
-        return TiledPagedList(
-                ListDataSource(listData), mMainThread, mBackgroundThread, boundaryCallback,
-                PagedList.Config.Builder()
-                        .setPageSize(PAGE_SIZE)
-                        .setInitialLoadSizeHint(PAGE_SIZE * initPageCount)
-                        .setPrefetchDistance(prefetchDistance)
-                        .build(),
-                loadPosition)
-    }
-
-    @Test
-    fun getDataSource() {
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
-        assertTrue(pagedList.dataSource is ListDataSource<Item>)
-
-        // snapshot keeps same DataSource
-        assertSame(pagedList.dataSource,
-                (pagedList.snapshot() as SnapshotPagedList<Item>).dataSource)
-    }
-
-    @Test
-    fun initialLoad_onePage() {
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
-        verifyLoadedPages(pagedList, 0, 1)
-    }
-
-    @Test
-    fun initialLoad_onePageOffset() {
-        val pagedList = createTiledPagedList(loadPosition = 10, initPageCount = 1)
-        verifyLoadedPages(pagedList, 0, 1)
-    }
-
-    @Test
-    fun initialLoad_full() {
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 100)
-        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
-    }
-
-    @Test
-    fun initialLoad_end() {
-        val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
-        verifyLoadedPages(pagedList, 3, 4)
-    }
-
-    @Test
-    fun initialLoad_multiple() {
-        val pagedList = createTiledPagedList(loadPosition = 9, initPageCount = 2)
-        verifyLoadedPages(pagedList, 0, 1)
-    }
-
-    @Test
-    fun initialLoad_offset() {
-        val pagedList = createTiledPagedList(loadPosition = 41, initPageCount = 2)
-        verifyLoadedPages(pagedList, 3, 4)
-    }
-
-    @Test
-    fun initialLoad_initializesLastKey() {
-        val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
-        assertEquals(44, pagedList.lastKey)
-    }
-
-    @Test
-    fun initialLoadAsync() {
-        val dataSource = AsyncListDataSource(ITEMS)
-        val pagedList = TiledPagedList(
-                dataSource, mMainThread, mBackgroundThread, null,
-                PagedList.Config.Builder().setPageSize(10).build(), 0)
-
-        assertTrue(pagedList.isEmpty())
-        drain()
-        assertTrue(pagedList.isEmpty())
-        dataSource.flush()
-        assertTrue(pagedList.isEmpty())
-        mBackgroundThread.executeAll()
-        assertTrue(pagedList.isEmpty())
-
-        // Data source defers callbacks until flush, which posts result to main thread
-        mMainThread.executeAll()
-        assertFalse(pagedList.isEmpty())
-    }
-
-    @Test
-    fun addWeakCallbackEmpty() {
-        val dataSource = AsyncListDataSource(ITEMS)
-        val pagedList = TiledPagedList(
-                dataSource, mMainThread, mBackgroundThread, null,
-                PagedList.Config.Builder().setPageSize(10).build(), 0)
-
-        // capture empty snapshot
-        val emptySnapshot = pagedList.snapshot()
-        assertTrue(pagedList.isEmpty())
-        assertTrue(emptySnapshot.isEmpty())
-
-        // data added in asynchronously
-        dataSource.flush()
-        drain()
-        assertFalse(pagedList.isEmpty())
-
-        // verify that adding callback works with empty start point
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(emptySnapshot, callback)
-        verify(callback).onInserted(0, pagedList.size)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun append() {
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyLoadedPages(pagedList, 0, 1)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(15)
-
-        verifyLoadedPages(pagedList, 0, 1)
-
-        drain()
-
-        verifyLoadedPages(pagedList, 0, 1, 2)
-        verify(callback).onChanged(20, 10)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun prepend() {
-        val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyLoadedPages(pagedList, 3, 4)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(35)
-        drain()
-
-        verifyLoadedPages(pagedList, 2, 3, 4)
-        verify<PagedList.Callback>(callback).onChanged(20, 10)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun loadWithGap() {
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyLoadedPages(pagedList, 0, 1)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(44)
-        drain()
-
-        verifyLoadedPages(pagedList, 0, 1, 3, 4)
-        verify(callback).onChanged(30, 10)
-        verify(callback).onChanged(40, 5)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun tinyPrefetchTest() {
-        val pagedList = createTiledPagedList(
-                loadPosition = 0, initPageCount = 1, prefetchDistance = 1)
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(null, callback)
-        verifyLoadedPages(pagedList, 0, 1)
-        verifyZeroInteractions(callback)
-
-        pagedList.loadAround(33)
-        drain()
-
-        verifyLoadedPages(pagedList, 0, 1, 3)
-        verify(callback).onChanged(30, 10)
-        verifyNoMoreInteractions(callback)
-
-        pagedList.loadAround(44)
-        drain()
-
-        verifyLoadedPages(pagedList, 0, 1, 3, 4)
-        verify(callback).onChanged(40, 5)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun appendCallbackAddedLate() {
-        val pagedList = createTiledPagedList(
-                loadPosition = 0, initPageCount = 1, prefetchDistance = 0)
-        verifyLoadedPages(pagedList, 0, 1)
-
-        pagedList.loadAround(25)
-        drain()
-        verifyLoadedPages(pagedList, 0, 1, 2)
-
-        // snapshot at 30 items
-        val snapshot = pagedList.snapshot()
-        verifyLoadedPages(snapshot, 0, 1, 2)
-
-        pagedList.loadAround(35)
-        pagedList.loadAround(44)
-        drain()
-        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
-        verifyLoadedPages(snapshot, 0, 1, 2)
-
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(snapshot, callback)
-        verify(callback).onChanged(30, 20)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun prependCallbackAddedLate() {
-        val pagedList = createTiledPagedList(
-                loadPosition = 44, initPageCount = 2, prefetchDistance = 0)
-        verifyLoadedPages(pagedList, 3, 4)
-
-        pagedList.loadAround(25)
-        drain()
-        verifyLoadedPages(pagedList, 2, 3, 4)
-
-        // snapshot at 30 items
-        val snapshot = pagedList.snapshot()
-        verifyLoadedPages(snapshot, 2, 3, 4)
-
-        pagedList.loadAround(15)
-        pagedList.loadAround(5)
-        drain()
-        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
-        verifyLoadedPages(snapshot, 2, 3, 4)
-
-        val callback = mock(PagedList.Callback::class.java)
-        pagedList.addWeakCallback(snapshot, callback)
-        verify(callback).onChanged(0, 20)
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun placeholdersDisabled() {
-        // disable placeholders with config, so we create a contiguous version of the pagedlist
-        val config = PagedList.Config.Builder()
-                .setPageSize(PAGE_SIZE)
-                .setPrefetchDistance(PAGE_SIZE)
-                .setInitialLoadSizeHint(PAGE_SIZE)
-                .setEnablePlaceholders(false)
-                .build()
-        val pagedList = PagedList.Builder<Int, Item>(ListDataSource(ITEMS), config)
-                .setMainThreadExecutor(mMainThread)
-                .setBackgroundThreadExecutor(mBackgroundThread)
-                .setInitialKey(20)
-                .build()
-
-        assertTrue(pagedList.isContiguous)
-
-        @Suppress("UNCHECKED_CAST")
-        val contiguousPagedList = pagedList as ContiguousPagedList<Int, Item>
-        assertEquals(0, contiguousPagedList.mStorage.leadingNullCount)
-        assertEquals(PAGE_SIZE, contiguousPagedList.mStorage.storageCount)
-        assertEquals(0, contiguousPagedList.mStorage.trailingNullCount)
-    }
-
-    @Test
-    fun boundaryCallback_empty() {
-        @Suppress("UNCHECKED_CAST")
-        val boundaryCallback =
-                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1,
-                listData = ArrayList(), boundaryCallback = boundaryCallback)
-        assertEquals(0, pagedList.size)
-
-        // nothing yet
-        verifyNoMoreInteractions(boundaryCallback)
-
-        // onZeroItemsLoaded posted, since creation often happens on BG thread
-        drain()
-        verify(boundaryCallback).onZeroItemsLoaded()
-        verifyNoMoreInteractions(boundaryCallback)
-    }
-
-    @Test
-    fun boundaryCallback_immediate() {
-        @Suppress("UNCHECKED_CAST")
-        val boundaryCallback =
-                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1,
-                listData = ITEMS.subList(0, 2), boundaryCallback = boundaryCallback)
-        assertEquals(2, pagedList.size)
-
-        // nothing yet
-        verifyZeroInteractions(boundaryCallback)
-
-        // callbacks posted, since creation often happens on BG thread
-        drain()
-        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS[0])
-        verify(boundaryCallback).onItemAtEndLoaded(ITEMS[1])
-        verifyNoMoreInteractions(boundaryCallback)
-    }
-
-    @Test
-    fun boundaryCallback_delayedUntilLoaded() {
-        @Suppress("UNCHECKED_CAST")
-        val boundaryCallback =
-                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
-        val pagedList = createTiledPagedList(loadPosition = 20, initPageCount = 1,
-                boundaryCallback = boundaryCallback)
-        verifyLoadedPages(pagedList, 1, 2) // 0, 3, and 4 not loaded yet
-
-        // nothing yet, even after drain
-        verifyZeroInteractions(boundaryCallback)
-        drain()
-        verifyZeroInteractions(boundaryCallback)
-
-        pagedList.loadAround(0)
-        pagedList.loadAround(44)
-
-        // still nothing, since items aren't loaded...
-        verifyZeroInteractions(boundaryCallback)
-
-        drain()
-        // first/last items loaded now, so callbacks dispatched
-        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
-        verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
-        verifyNoMoreInteractions(boundaryCallback)
-    }
-
-    @Test
-    fun boundaryCallback_delayedUntilNearbyAccess() {
-        @Suppress("UNCHECKED_CAST")
-        val boundaryCallback =
-                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
-        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 5,
-                prefetchDistance = 2, boundaryCallback = boundaryCallback)
-        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
-
-        // all items loaded, but no access near ends, so no callbacks
-        verifyZeroInteractions(boundaryCallback)
-        drain()
-        verifyZeroInteractions(boundaryCallback)
-
-        pagedList.loadAround(0)
-        pagedList.loadAround(44)
-
-        // callbacks not posted immediately
-        verifyZeroInteractions(boundaryCallback)
-
-        drain()
-
-        // items accessed, so now posted callbacks are run
-        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
-        verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
-        verifyNoMoreInteractions(boundaryCallback)
-    }
-
-    private fun drain() {
-        var executed: Boolean
-        do {
-            executed = mBackgroundThread.executeAll()
-            executed = mMainThread.executeAll() || executed
-        } while (executed)
-    }
-
-    companion object {
-        // use a page size that's not an even divisor of ITEMS.size() to test end conditions
-        private val PAGE_SIZE = 10
-
-        private val ITEMS = List(45) { Item(it) }
-    }
-}
diff --git a/paging/common/src/test/java/androidx/paging/AsyncListDataSource.kt b/paging/common/src/test/java/androidx/paging/AsyncListDataSource.kt
new file mode 100644
index 0000000..6a4ff37
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/AsyncListDataSource.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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 androidx.paging
+
+class AsyncListDataSource<T>(list: List<T>)
+    : PositionalDataSource<T>() {
+    private val workItems: MutableList<() -> Unit> = ArrayList()
+    private val listDataSource = ListDataSource(list)
+
+    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
+        workItems.add {
+            listDataSource.loadInitial(params, callback)
+        }
+    }
+
+    override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
+        workItems.add {
+            listDataSource.loadRange(params, callback)
+        }
+    }
+
+    fun flush() {
+        workItems.map { it() }
+        workItems.clear()
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
new file mode 100644
index 0000000..2a2b8a4
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
@@ -0,0 +1,519 @@
+/*
+ * 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 androidx.paging
+
+import androidx.arch.core.util.Function
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import java.util.concurrent.Executor
+
+@RunWith(Parameterized::class)
+class ContiguousPagedListTest(private val mCounted: Boolean) {
+    private val mMainThread = TestExecutor()
+    private val mBackgroundThread = TestExecutor()
+
+    private class Item(position: Int) {
+        val name: String = "Item $position"
+
+        override fun toString(): String {
+            return name
+        }
+    }
+
+    private inner class TestSource(val listData: List<Item> = ITEMS)
+            : ContiguousDataSource<Int, Item>() {
+        override fun dispatchLoadInitial(
+                key: Int?,
+                initialLoadSize: Int,
+                pageSize: Int,
+                enablePlaceholders: Boolean,
+                mainThreadExecutor: Executor,
+                receiver: PageResult.Receiver<Item>) {
+
+            val convertPosition = key ?: 0
+            val position = Math.max(0, (convertPosition - initialLoadSize / 2))
+            val data = getClampedRange(position, position + initialLoadSize)
+            val trailingUnloadedCount = listData.size - position - data.size
+
+            if (enablePlaceholders && mCounted) {
+                receiver.onPageResult(PageResult.INIT,
+                        PageResult(data, position, trailingUnloadedCount, 0))
+            } else {
+                // still must pass offset, even if not counted
+                receiver.onPageResult(PageResult.INIT,
+                        PageResult(data, position))
+            }
+        }
+
+        override fun dispatchLoadAfter(
+                currentEndIndex: Int,
+                currentEndItem: Item,
+                pageSize: Int,
+                mainThreadExecutor: Executor,
+                receiver: PageResult.Receiver<Item>) {
+            val startIndex = currentEndIndex + 1
+            val data = getClampedRange(startIndex, startIndex + pageSize)
+
+            mainThreadExecutor.execute {
+                receiver.onPageResult(PageResult.APPEND, PageResult(data, 0, 0, 0))
+            }
+        }
+
+        override fun dispatchLoadBefore(
+                currentBeginIndex: Int,
+                currentBeginItem: Item,
+                pageSize: Int,
+                mainThreadExecutor: Executor,
+                receiver: PageResult.Receiver<Item>) {
+
+            val startIndex = currentBeginIndex - 1
+            val data = getClampedRange(startIndex - pageSize + 1, startIndex + 1)
+
+            mainThreadExecutor.execute {
+                receiver.onPageResult(PageResult.PREPEND, PageResult(data, 0, 0, 0))
+            }
+        }
+
+        override fun getKey(position: Int, item: Item?): Int {
+            return 0
+        }
+
+        private fun getClampedRange(startInc: Int, endExc: Int): List<Item> {
+            return listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
+        }
+
+        override fun <ToValue : Any?> mapByPage(function: Function<List<Item>, List<ToValue>>):
+                DataSource<Int, ToValue> {
+            throw UnsupportedOperationException()
+        }
+
+        override fun <ToValue : Any?> map(function: Function<Item, ToValue>):
+                DataSource<Int, ToValue> {
+            throw UnsupportedOperationException()
+        }
+    }
+
+    private fun verifyRange(start: Int, count: Int, actual: PagedStorage<Item>) {
+        if (mCounted) {
+            // assert nulls + content
+            val expected = arrayOfNulls<Item>(ITEMS.size)
+            System.arraycopy(ITEMS.toTypedArray(), start, expected, start, count)
+            assertArrayEquals(expected, actual.toTypedArray())
+
+            val expectedTrailing = ITEMS.size - start - count
+            assertEquals(ITEMS.size, actual.size)
+            assertEquals((ITEMS.size - start - expectedTrailing),
+                    actual.storageCount)
+            assertEquals(start, actual.leadingNullCount)
+            assertEquals(expectedTrailing, actual.trailingNullCount)
+        } else {
+            assertEquals(ITEMS.subList(start, start + count), actual)
+
+            assertEquals(count, actual.size)
+            assertEquals(actual.size, actual.storageCount)
+            assertEquals(0, actual.leadingNullCount)
+            assertEquals(0, actual.trailingNullCount)
+        }
+    }
+
+    private fun verifyRange(start: Int, count: Int, actual: PagedList<Item>) {
+        verifyRange(start, count, actual.mStorage)
+    }
+
+    private fun createCountedPagedList(
+            initialPosition: Int,
+            pageSize: Int = 20,
+            initLoadSize: Int = 40,
+            prefetchDistance: Int = 20,
+            listData: List<Item> = ITEMS,
+            boundaryCallback: PagedList.BoundaryCallback<Item>? = null,
+            lastLoad: Int = ContiguousPagedList.LAST_LOAD_UNSPECIFIED
+    ): ContiguousPagedList<Int, Item> {
+        return ContiguousPagedList(
+                TestSource(listData), mMainThread, mBackgroundThread, boundaryCallback,
+                PagedList.Config.Builder()
+                        .setInitialLoadSizeHint(initLoadSize)
+                        .setPageSize(pageSize)
+                        .setPrefetchDistance(prefetchDistance)
+                        .build(),
+                initialPosition,
+                lastLoad)
+    }
+
+    @Test
+    fun construct() {
+        val pagedList = createCountedPagedList(0)
+        verifyRange(0, 40, pagedList)
+    }
+
+    @Test
+    fun getDataSource() {
+        val pagedList = createCountedPagedList(0)
+        assertTrue(pagedList.dataSource is TestSource)
+
+        // snapshot keeps same DataSource
+        assertSame(pagedList.dataSource,
+                (pagedList.snapshot() as SnapshotPagedList<Item>).dataSource)
+    }
+
+    private fun verifyCallback(callback: PagedList.Callback, countedPosition: Int,
+            uncountedPosition: Int) {
+        if (mCounted) {
+            verify(callback).onChanged(countedPosition, 20)
+        } else {
+            verify(callback).onInserted(uncountedPosition, 20)
+        }
+    }
+
+    @Test
+    fun append() {
+        val pagedList = createCountedPagedList(0)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyRange(0, 40, pagedList)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(35)
+        drain()
+
+        verifyRange(0, 60, pagedList)
+        verifyCallback(callback, 40, 40)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun prepend() {
+        val pagedList = createCountedPagedList(80)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyRange(60, 40, pagedList)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(if (mCounted) 65 else 5)
+        drain()
+
+        verifyRange(40, 60, pagedList)
+        verifyCallback(callback, 40, 0)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun outwards() {
+        val pagedList = createCountedPagedList(50)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyRange(30, 40, pagedList)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(if (mCounted) 65 else 35)
+        drain()
+
+        verifyRange(30, 60, pagedList)
+        verifyCallback(callback, 70, 40)
+        verifyNoMoreInteractions(callback)
+
+        pagedList.loadAround(if (mCounted) 35 else 5)
+        drain()
+
+        verifyRange(10, 80, pagedList)
+        verifyCallback(callback, 10, 0)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun multiAppend() {
+        val pagedList = createCountedPagedList(0)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyRange(0, 40, pagedList)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(55)
+        drain()
+
+        verifyRange(0, 80, pagedList)
+        verifyCallback(callback, 40, 40)
+        verifyCallback(callback, 60, 60)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun distantPrefetch() {
+        val pagedList = createCountedPagedList(0,
+                initLoadSize = 10, pageSize = 10, prefetchDistance = 30)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyRange(0, 10, pagedList)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(5)
+        drain()
+
+        verifyRange(0, 40, pagedList)
+
+        pagedList.loadAround(6)
+        drain()
+
+        // although our prefetch window moves forward, no new load triggered
+        verifyRange(0, 40, pagedList)
+    }
+
+    @Test
+    fun appendCallbackAddedLate() {
+        val pagedList = createCountedPagedList(0)
+        verifyRange(0, 40, pagedList)
+
+        pagedList.loadAround(35)
+        drain()
+        verifyRange(0, 60, pagedList)
+
+        // snapshot at 60 items
+        val snapshot = pagedList.snapshot() as PagedList<Item>
+        verifyRange(0, 60, snapshot)
+
+        // load more items...
+        pagedList.loadAround(55)
+        drain()
+        verifyRange(0, 80, pagedList)
+        verifyRange(0, 60, snapshot)
+
+        // and verify the snapshot hasn't received them
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(snapshot, callback)
+        verifyCallback(callback, 60, 60)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun prependCallbackAddedLate() {
+        val pagedList = createCountedPagedList(80)
+        verifyRange(60, 40, pagedList)
+
+        pagedList.loadAround(if (mCounted) 65 else 5)
+        drain()
+        verifyRange(40, 60, pagedList)
+
+        // snapshot at 60 items
+        val snapshot = pagedList.snapshot() as PagedList<Item>
+        verifyRange(40, 60, snapshot)
+
+        pagedList.loadAround(if (mCounted) 45 else 5)
+        drain()
+        verifyRange(20, 80, pagedList)
+        verifyRange(40, 60, snapshot)
+
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(snapshot, callback)
+        verifyCallback(callback, 40, 0)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun initialLoad_lastLoad() {
+        val pagedList = createCountedPagedList(
+                initialPosition = 0,
+                initLoadSize = 20,
+                lastLoad = 4)
+        // last load is param passed
+        assertEquals(4, pagedList.mLastLoad)
+        verifyRange(0, 20, pagedList)
+    }
+
+    @Test
+    fun initialLoad_lastLoadComputed() {
+        val pagedList = createCountedPagedList(
+                initialPosition = 0,
+                initLoadSize = 20,
+                lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+        // last load is middle of initial load
+        assertEquals(10, pagedList.mLastLoad)
+        verifyRange(0, 20, pagedList)
+    }
+
+    @Test
+    fun initialLoadAsync() {
+        // Note: ignores Parameterized param
+        val asyncDataSource = AsyncListDataSource(ITEMS)
+        val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
+        val pagedList = ContiguousPagedList(
+                dataSource, mMainThread, mBackgroundThread, null,
+                PagedList.Config.Builder().setPageSize(10).build(), null,
+                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+
+        assertTrue(pagedList.isEmpty())
+        drain()
+        assertTrue(pagedList.isEmpty())
+        asyncDataSource.flush()
+        assertTrue(pagedList.isEmpty())
+        mBackgroundThread.executeAll()
+        assertTrue(pagedList.isEmpty())
+        verifyZeroInteractions(callback)
+
+        // Data source defers callbacks until flush, which posts result to main thread
+        mMainThread.executeAll()
+        assertFalse(pagedList.isEmpty())
+        // callback onInsert called once with initial size
+        verify(callback).onInserted(0, pagedList.size)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun addWeakCallbackEmpty() {
+        // Note: ignores Parameterized param
+        val asyncDataSource = AsyncListDataSource(ITEMS)
+        val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
+        val pagedList = ContiguousPagedList(
+                dataSource, mMainThread, mBackgroundThread, null,
+                PagedList.Config.Builder().setPageSize(10).build(), null,
+                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+        val callback = mock(PagedList.Callback::class.java)
+
+        // capture empty snapshot
+        val emptySnapshot = pagedList.snapshot()
+        assertTrue(pagedList.isEmpty())
+        assertTrue(emptySnapshot.isEmpty())
+
+        // verify that adding callback notifies nothing going from empty -> empty
+        pagedList.addWeakCallback(emptySnapshot, callback)
+        verifyZeroInteractions(callback)
+        pagedList.removeWeakCallback(callback)
+
+        // data added in asynchronously
+        asyncDataSource.flush()
+        drain()
+        assertFalse(pagedList.isEmpty())
+
+        // verify that adding callback notifies insert going from empty -> content
+        pagedList.addWeakCallback(emptySnapshot, callback)
+        verify(callback).onInserted(0, pagedList.size)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun boundaryCallback_empty() {
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val pagedList = createCountedPagedList(0,
+                listData = ArrayList(), boundaryCallback = boundaryCallback)
+        assertEquals(0, pagedList.size)
+
+        // nothing yet
+        verifyNoMoreInteractions(boundaryCallback)
+
+        // onZeroItemsLoaded posted, since creation often happens on BG thread
+        drain()
+        verify(boundaryCallback).onZeroItemsLoaded()
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    @Test
+    fun boundaryCallback_singleInitialLoad() {
+        val shortList = ITEMS.subList(0, 4)
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val pagedList = createCountedPagedList(0, listData = shortList,
+                initLoadSize = shortList.size, boundaryCallback = boundaryCallback)
+        assertEquals(shortList.size, pagedList.size)
+
+        // nothing yet
+        verifyNoMoreInteractions(boundaryCallback)
+
+        // onItemAtFrontLoaded / onItemAtEndLoaded posted, since creation often happens on BG thread
+        drain()
+        pagedList.loadAround(0)
+        drain()
+        verify(boundaryCallback).onItemAtFrontLoaded(shortList.first())
+        verify(boundaryCallback).onItemAtEndLoaded(shortList.last())
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    @Test
+    fun boundaryCallback_delayed() {
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val pagedList = createCountedPagedList(90,
+                initLoadSize = 20, prefetchDistance = 5, boundaryCallback = boundaryCallback)
+        verifyRange(80, 20, pagedList)
+
+        // nothing yet
+        verifyZeroInteractions(boundaryCallback)
+        drain()
+        verifyZeroInteractions(boundaryCallback)
+
+        // loading around last item causes onItemAtEndLoaded
+        pagedList.loadAround(if (mCounted) 99 else 19)
+        drain()
+        verifyRange(80, 20, pagedList)
+        verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
+        verifyNoMoreInteractions(boundaryCallback)
+
+        // prepending doesn't trigger callback...
+        pagedList.loadAround(if (mCounted) 80 else 0)
+        drain()
+        verifyRange(60, 40, pagedList)
+        verifyZeroInteractions(boundaryCallback)
+
+        // ...load rest of data, still no dispatch...
+        pagedList.loadAround(if (mCounted) 60 else 0)
+        drain()
+        pagedList.loadAround(if (mCounted) 40 else 0)
+        drain()
+        pagedList.loadAround(if (mCounted) 20 else 0)
+        drain()
+        verifyRange(0, 100, pagedList)
+        verifyZeroInteractions(boundaryCallback)
+
+        // ... finally try prepend, see 0 items, which will dispatch front callback
+        pagedList.loadAround(0)
+        drain()
+        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    private fun drain() {
+        var executed: Boolean
+        do {
+            executed = mBackgroundThread.executeAll()
+            executed = mMainThread.executeAll() || executed
+        } while (executed)
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "counted:{0}")
+        fun parameters(): Array<Array<Boolean>> {
+            return arrayOf(arrayOf(true), arrayOf(false))
+        }
+
+        private val ITEMS = List(100) { Item(it) }
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/Executors.kt b/paging/common/src/test/java/androidx/paging/Executors.kt
new file mode 100644
index 0000000..16458bc
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/Executors.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 androidx.paging
+
+import org.junit.Assert.fail
+import java.util.LinkedList
+import java.util.concurrent.Executor
+
+class TestExecutor : Executor {
+    private val mTasks = LinkedList<Runnable>()
+
+    override fun execute(runnable: Runnable) {
+        mTasks.add(runnable)
+    }
+
+    internal fun executeAll(): Boolean {
+        val consumed = !mTasks.isEmpty()
+
+        var task = mTasks.poll()
+        while (task != null) {
+            task.run()
+            task = mTasks.poll()
+        }
+        return consumed
+    }
+}
+
+class FailExecutor(val string: String = "Executor expected to be unused") : Executor {
+    override fun execute(runnable: Runnable?) {
+        fail(string)
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
new file mode 100644
index 0000000..bdb001a
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -0,0 +1,457 @@
+/*
+ * 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 androidx.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@RunWith(JUnit4::class)
+class ItemKeyedDataSourceTest {
+
+    // ----- STANDARD -----
+
+    private fun loadInitial(dataSource: ItemDataSource, key: Key?, initialLoadSize: Int,
+            enablePlaceholders: Boolean): PageResult<Item> {
+        @Suppress("UNCHECKED_CAST")
+        val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Item>
+        @Suppress("UNCHECKED_CAST")
+        val captor = ArgumentCaptor.forClass(PageResult::class.java)
+                as ArgumentCaptor<PageResult<Item>>
+
+        dataSource.dispatchLoadInitial(key, initialLoadSize,
+                /* ignored pageSize */ 10, enablePlaceholders, FailExecutor(), receiver)
+
+        verify(receiver).onPageResult(anyInt(), captor.capture())
+        verifyNoMoreInteractions(receiver)
+        assertNotNull(captor.value)
+        return captor.value
+    }
+
+    @Test
+    fun loadInitial() {
+        val dataSource = ItemDataSource()
+        val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[49]), 10, true)
+
+        assertEquals(45, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
+        assertEquals(45, result.trailingNulls)
+    }
+
+    @Test
+    fun loadInitial_keyMatchesSingleItem() {
+        val dataSource = ItemDataSource(items = ITEMS_BY_NAME_ID.subList(0, 1))
+
+        // this is tricky, since load after and load before with the passed key will fail
+        val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[0]), 20, true)
+
+        assertEquals(0, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.page)
+        assertEquals(0, result.trailingNulls)
+    }
+
+    @Test
+    fun loadInitial_keyMatchesLastItem() {
+        val dataSource = ItemDataSource()
+
+        // tricky, because load after key is empty, so another load before and load after required
+        val key = dataSource.getKey(ITEMS_BY_NAME_ID.last())
+        val result = loadInitial(dataSource, key, 20, true)
+
+        assertEquals(90, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(90, 100), result.page)
+        assertEquals(0, result.trailingNulls)
+    }
+
+    @Test
+    fun loadInitial_nullKey() {
+        val dataSource = ItemDataSource()
+
+        // dispatchLoadInitial(null, count) == dispatchLoadInitial(count)
+        val result = loadInitial(dataSource, null, 10, true)
+
+        assertEquals(0, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
+        assertEquals(90, result.trailingNulls)
+    }
+
+    @Test
+    fun loadInitial_keyPastEndOfList() {
+        val dataSource = ItemDataSource()
+
+        // if key is past entire data set, should return last items in data set
+        val key = Key("fz", 0)
+        val result = loadInitial(dataSource, key, 10, true)
+
+        // NOTE: ideally we'd load 10 items here, but it adds complexity and unpredictability to
+        // do: load after was empty, so pass full size to load before, since this can incur larger
+        // loads than requested (see keyMatchesLastItem test)
+        assertEquals(95, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.page)
+        assertEquals(0, result.trailingNulls)
+    }
+
+    // ----- UNCOUNTED -----
+
+    @Test
+    fun loadInitial_disablePlaceholders() {
+        val dataSource = ItemDataSource()
+
+        // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
+        val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
+        val result = loadInitial(dataSource, key, 10, false)
+
+        assertEquals(0, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
+        assertEquals(0, result.trailingNulls)
+    }
+
+    @Test
+    fun loadInitial_uncounted() {
+        val dataSource = ItemDataSource(counted = false)
+
+        // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
+        val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
+        val result = loadInitial(dataSource, key, 10, true)
+
+        assertEquals(0, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
+        assertEquals(0, result.trailingNulls)
+    }
+
+    @Test
+    fun loadInitial_nullKey_uncounted() {
+        val dataSource = ItemDataSource(counted = false)
+
+        // dispatchLoadInitial(null, count) == dispatchLoadInitial(count)
+        val result = loadInitial(dataSource, null, 10, true)
+
+        assertEquals(0, result.leadingNulls)
+        assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
+        assertEquals(0, result.trailingNulls)
+    }
+
+    // ----- EMPTY -----
+
+    @Test
+    fun loadInitial_empty() {
+        val dataSource = ItemDataSource(items = ArrayList())
+
+        // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
+        val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
+        val result = loadInitial(dataSource, key, 10, true)
+
+        assertEquals(0, result.leadingNulls)
+        assertTrue(result.page.isEmpty())
+        assertEquals(0, result.trailingNulls)
+    }
+
+    @Test
+    fun loadInitial_nullKey_empty() {
+        val dataSource = ItemDataSource(items = ArrayList())
+        val result = loadInitial(dataSource, null, 10, true)
+
+        assertEquals(0, result.leadingNulls)
+        assertTrue(result.page.isEmpty())
+        assertEquals(0, result.trailingNulls)
+    }
+
+    // ----- Other behavior -----
+
+    @Test
+    fun loadBefore() {
+        val dataSource = ItemDataSource()
+        @Suppress("UNCHECKED_CAST")
+        val callback = mock(ItemKeyedDataSource.LoadCallback::class.java)
+                as ItemKeyedDataSource.LoadCallback<Item>
+
+        dataSource.loadBefore(
+                ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback)
+
+        @Suppress("UNCHECKED_CAST")
+        val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
+        verify(callback).onResult(argument.capture())
+        verifyNoMoreInteractions(callback)
+
+        val observed = argument.value
+
+        assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
+    }
+
+    internal data class Key(val name: String, val id: Int)
+
+    internal data class Item(
+            val name: String, val id: Int, val balance: Double, val address: String)
+
+    internal class ItemDataSource(private val counted: Boolean = true,
+                                  private val items: List<Item> = ITEMS_BY_NAME_ID)
+            : ItemKeyedDataSource<Key, Item>() {
+
+        override fun loadInitial(
+                params: LoadInitialParams<Key>,
+                callback: LoadInitialCallback<Item>) {
+            val key = params.requestedInitialKey ?: Key("", Integer.MAX_VALUE)
+            val start = Math.max(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
+            val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
+
+            if (params.placeholdersEnabled && counted) {
+                callback.onResult(items.subList(start, endExclusive), start, items.size)
+            } else {
+                callback.onResult(items.subList(start, endExclusive))
+            }
+        }
+
+        override fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Item>) {
+            val start = findFirstIndexAfter(params.key)
+            val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
+
+            callback.onResult(items.subList(start, endExclusive))
+        }
+
+        override fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Item>) {
+            val firstIndexBefore = findFirstIndexBefore(params.key)
+            val endExclusive = Math.max(0, firstIndexBefore + 1)
+            val start = Math.max(0, firstIndexBefore - params.requestedLoadSize + 1)
+
+            callback.onResult(items.subList(start, endExclusive))
+        }
+
+        override fun getKey(item: Item): Key {
+            return Key(item.name, item.id)
+        }
+
+        private fun findFirstIndexAfter(key: Key): Int {
+            return items.indices.firstOrNull {
+                KEY_COMPARATOR.compare(key, getKey(items[it])) < 0
+            } ?: items.size
+        }
+
+        private fun findFirstIndexBefore(key: Key): Int {
+            return items.indices.reversed().firstOrNull {
+                KEY_COMPARATOR.compare(key, getKey(items[it])) > 0
+            } ?: -1
+        }
+    }
+
+    private fun performLoadInitial(
+            invalidateDataSource: Boolean = false,
+            callbackInvoker: (callback: ItemKeyedDataSource.LoadInitialCallback<String>) -> Unit) {
+        val dataSource = object : ItemKeyedDataSource<String, String>() {
+            override fun getKey(item: String): String {
+                return ""
+            }
+
+            override fun loadInitial(
+                    params: LoadInitialParams<String>,
+                    callback: LoadInitialCallback<String>) {
+                if (invalidateDataSource) {
+                    // invalidate data source so it's invalid when onResult() called
+                    invalidate()
+                }
+                callbackInvoker(callback)
+            }
+
+            override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String>) {
+                fail("loadAfter not expected")
+            }
+
+            override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String>) {
+                fail("loadBefore not expected")
+            }
+        }
+
+        ContiguousPagedList<String, String>(
+                dataSource, FailExecutor(), FailExecutor(), null,
+                PagedList.Config.Builder()
+                        .setPageSize(10)
+                        .build(),
+                "",
+                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+    }
+
+    @Test
+    fun loadInitialCallbackSuccess() = performLoadInitial {
+        // LoadInitialCallback correct usage
+        it.onResult(listOf("a", "b"), 0, 2)
+    }
+
+    @Test
+    fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial {
+        // Keyed LoadInitialCallback *can* accept result that's not a multiple of page size
+        val elevenLetterList = List(11) { "" + 'a' + it }
+        it.onResult(elevenLetterList, 0, 12)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackListTooBig() = performLoadInitial {
+        // LoadInitialCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b", "c"), 0, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackPositionTooLarge() = performLoadInitial {
+        // LoadInitialCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b"), 1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackPositionNegative() = performLoadInitial {
+        // LoadInitialCallback can't accept negative position
+        it.onResult(listOf("a", "b", "c"), -1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
+        // LoadInitialCallback can't accept empty result unless data set is empty
+        it.onResult(emptyList(), 0, 2)
+    }
+
+    @Test
+    fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
+        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
+        it.onResult(emptyList(), 0, 1)
+    }
+
+    private abstract class WrapperDataSource<K, A, B>(private val source: ItemKeyedDataSource<K, A>)
+            : ItemKeyedDataSource<K, B>() {
+        private val invalidatedCallback = DataSource.InvalidatedCallback {
+            invalidate()
+            removeCallback()
+        }
+
+        init {
+            source.addInvalidatedCallback(invalidatedCallback)
+        }
+
+        private fun removeCallback() {
+            removeInvalidatedCallback(invalidatedCallback)
+        }
+
+        override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
+            source.loadInitial(params, object : LoadInitialCallback<A>() {
+                override fun onResult(data: List<A>, position: Int, totalCount: Int) {
+                    callback.onResult(convert(data), position, totalCount)
+                }
+
+                override fun onResult(data: MutableList<A>) {
+                    callback.onResult(convert(data))
+                }
+            })
+        }
+
+        override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
+            source.loadAfter(params, object : LoadCallback<A>() {
+                override fun onResult(data: MutableList<A>) {
+                    callback.onResult(convert(data))
+                }
+            })
+        }
+
+        override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
+            source.loadBefore(params, object : LoadCallback<A>() {
+                override fun onResult(data: MutableList<A>) {
+                    callback.onResult(convert(data))
+                }
+            })
+        }
+
+        protected abstract fun convert(source: List<A>): List<B>
+    }
+
+    private data class DecoratedItem(val item: Item)
+
+    private class DecoratedWrapperDataSource(private val source: ItemKeyedDataSource<Key, Item>)
+            : WrapperDataSource<Key, Item, DecoratedItem>(source) {
+        override fun convert(source: List<Item>): List<DecoratedItem> {
+            return source.map { DecoratedItem(it) }
+        }
+
+        override fun getKey(item: DecoratedItem): Key {
+            return source.getKey(item.item)
+        }
+    }
+
+    private fun verifyWrappedDataSource(createWrapper:
+            (ItemKeyedDataSource<Key, Item>) -> ItemKeyedDataSource<Key, DecoratedItem>) {
+        // verify that it's possible to wrap an ItemKeyedDataSource, and add info to its data
+
+        val orig = ItemDataSource(items = ITEMS_BY_NAME_ID)
+        val wrapper = DecoratedWrapperDataSource(orig)
+
+        // load initial
+        @Suppress("UNCHECKED_CAST")
+        val loadInitialCallback = mock(ItemKeyedDataSource.LoadInitialCallback::class.java)
+                as ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>
+        val initKey = orig.getKey(ITEMS_BY_NAME_ID.first())
+        wrapper.loadInitial(ItemKeyedDataSource.LoadInitialParams(initKey, 10, false),
+                loadInitialCallback)
+        verify(loadInitialCallback).onResult(
+                ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
+        verifyNoMoreInteractions(loadInitialCallback)
+
+        @Suppress("UNCHECKED_CAST")
+        val loadCallback = mock(ItemKeyedDataSource.LoadCallback::class.java)
+                as ItemKeyedDataSource.LoadCallback<DecoratedItem>
+        val key = orig.getKey(ITEMS_BY_NAME_ID[20])
+        // load after
+        wrapper.loadAfter(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
+        verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(21, 31).map { DecoratedItem(it) })
+        verifyNoMoreInteractions(loadCallback)
+
+        // load before
+        wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
+        verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(10, 20).map { DecoratedItem(it) })
+        verifyNoMoreInteractions(loadCallback)
+    }
+
+    @Test
+    fun testManualWrappedDataSource() = verifyWrappedDataSource {
+        DecoratedWrapperDataSource(it)
+    }
+
+    @Test
+    fun testListConverterWrappedDataSource() = verifyWrappedDataSource {
+        it.mapByPage { it.map { DecoratedItem(it) } }
+    }
+
+    @Test
+    fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
+        it.map { DecoratedItem(it) }
+    }
+
+    companion object {
+        private val ITEM_COMPARATOR = compareBy<Item>({ it.name }).thenByDescending({ it.id })
+        private val KEY_COMPARATOR = compareBy<Key>({ it.name }).thenByDescending({ it.id })
+
+        private val ITEMS_BY_NAME_ID = List(100) {
+            val names = Array(10) { "f" + ('a' + it) }
+            Item(names[it % 10],
+                    it,
+                    Math.random() * 1000,
+                    (Math.random() * 200).toInt().toString() + " fake st.")
+        }.sortedWith(ITEM_COMPARATOR)
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
new file mode 100644
index 0000000..d86b87a
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright 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 androidx.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@RunWith(JUnit4::class)
+class PageKeyedDataSourceTest {
+    private val mMainThread = TestExecutor()
+    private val mBackgroundThread = TestExecutor()
+
+    internal data class Item(val name: String)
+
+    internal data class Page(val prev: String?, val data: List<Item>, val next: String?)
+
+    internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP)
+            : PageKeyedDataSource<String, Item>() {
+
+        private fun getPage(key: String): Page = data[key]!!
+
+        override fun loadInitial(
+                params: LoadInitialParams<String>,
+                callback: LoadInitialCallback<String, Item>) {
+            val page = getPage(INIT_KEY)
+            callback.onResult(page.data, page.prev, page.next)
+        }
+
+        override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
+            val page = getPage(params.key)
+            callback.onResult(page.data, page.prev)
+        }
+
+        override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
+            val page = getPage(params.key)
+            callback.onResult(page.data, page.next)
+        }
+    }
+
+    @Test
+    fun loadFullVerify() {
+        // validate paging entire ItemDataSource results in full, correctly ordered data
+        val pagedList = ContiguousPagedList<String, Item>(ItemDataSource(),
+                mMainThread, mBackgroundThread,
+                null, PagedList.Config.Builder().setPageSize(100).build(), null,
+                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+
+        // validate initial load
+        assertEquals(PAGE_MAP[INIT_KEY]!!.data, pagedList)
+
+        // flush the remaining loads
+        for (i in 0..PAGE_MAP.keys.size) {
+            pagedList.loadAround(0)
+            pagedList.loadAround(pagedList.size - 1)
+            drain()
+        }
+
+        // validate full load
+        assertEquals(ITEM_LIST, pagedList)
+    }
+
+    private fun performLoadInitial(invalidateDataSource: Boolean = false,
+            callbackInvoker:
+                    (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit) {
+        val dataSource = object : PageKeyedDataSource<String, String>() {
+            override fun loadInitial(
+                    params: LoadInitialParams<String>,
+                    callback: LoadInitialCallback<String, String>) {
+                if (invalidateDataSource) {
+                    // invalidate data source so it's invalid when onResult() called
+                    invalidate()
+                }
+                callbackInvoker(callback)
+            }
+
+            override fun loadBefore(
+                    params: LoadParams<String>,
+                    callback: LoadCallback<String, String>) {
+                fail("loadBefore not expected")
+            }
+
+            override fun loadAfter(
+                    params: LoadParams<String>,
+                    callback: LoadCallback<String, String>) {
+                fail("loadAfter not expected")
+            }
+        }
+
+        ContiguousPagedList<String, String>(
+                dataSource, FailExecutor(), FailExecutor(), null,
+                PagedList.Config.Builder()
+                        .setPageSize(10)
+                        .build(),
+                "",
+                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+    }
+
+    @Test
+    fun loadInitialCallbackSuccess() = performLoadInitial {
+        // LoadInitialCallback correct usage
+        it.onResult(listOf("a", "b"), 0, 2, null, null)
+    }
+
+    @Test
+    fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial {
+        // Keyed LoadInitialCallback *can* accept result that's not a multiple of page size
+        val elevenLetterList = List(11) { "" + 'a' + it }
+        it.onResult(elevenLetterList, 0, 12, null, null)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackListTooBig() = performLoadInitial {
+        // LoadInitialCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b", "c"), 0, 2, null, null)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackPositionTooLarge() = performLoadInitial {
+        // LoadInitialCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b"), 1, 2, null, null)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackPositionNegative() = performLoadInitial {
+        // LoadInitialCallback can't accept negative position
+        it.onResult(listOf("a", "b", "c"), -1, 2, null, null)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
+        // LoadInitialCallback can't accept empty result unless data set is empty
+        it.onResult(emptyList(), 0, 2, null, null)
+    }
+
+    @Test
+    fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
+        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
+        it.onResult(emptyList(), 0, 1, null, null)
+    }
+
+    private abstract class WrapperDataSource<K, A, B>(private val source: PageKeyedDataSource<K, A>)
+            : PageKeyedDataSource<K, B>() {
+        private val invalidatedCallback = DataSource.InvalidatedCallback {
+            invalidate()
+            removeCallback()
+        }
+
+        init {
+            source.addInvalidatedCallback(invalidatedCallback)
+        }
+
+        private fun removeCallback() {
+            removeInvalidatedCallback(invalidatedCallback)
+        }
+
+        override fun loadInitial(params: LoadInitialParams<K>,
+                callback: LoadInitialCallback<K, B>) {
+            source.loadInitial(params, object : LoadInitialCallback<K, A>() {
+                override fun onResult(data: List<A>, position: Int, totalCount: Int,
+                        previousPageKey: K?, nextPageKey: K?) {
+                    callback.onResult(convert(data), position, totalCount,
+                            previousPageKey, nextPageKey)
+                }
+
+                override fun onResult(data: MutableList<A>, previousPageKey: K?, nextPageKey: K?) {
+                    callback.onResult(convert(data), previousPageKey, nextPageKey)
+                }
+            })
+        }
+
+        override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<K, B>) {
+            source.loadBefore(params, object : LoadCallback<K, A>() {
+                override fun onResult(data: List<A>, adjacentPageKey: K?) {
+                    callback.onResult(convert(data), adjacentPageKey)
+                }
+            })
+        }
+
+        override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<K, B>) {
+            source.loadAfter(params, object : LoadCallback<K, A>() {
+                override fun onResult(data: List<A>, adjacentPageKey: K?) {
+                    callback.onResult(convert(data), adjacentPageKey)
+                }
+            })
+        }
+
+        protected abstract fun convert(source: List<A>): List<B>
+    }
+
+    private class StringWrapperDataSource<K, V>(source: PageKeyedDataSource<K, V>)
+            : WrapperDataSource<K, V, String>(source) {
+        override fun convert(source: List<V>): List<String> {
+            return source.map { it.toString() }
+        }
+    }
+
+    private fun verifyWrappedDataSource(createWrapper:
+            (PageKeyedDataSource<String, Item>) -> PageKeyedDataSource<String, String>) {
+        // verify that it's possible to wrap a PageKeyedDataSource, and add info to its data
+        val orig = ItemDataSource(data = PAGE_MAP)
+        val wrapper = createWrapper(orig)
+
+        // load initial
+        @Suppress("UNCHECKED_CAST")
+        val loadInitialCallback = mock(PageKeyedDataSource.LoadInitialCallback::class.java)
+                as PageKeyedDataSource.LoadInitialCallback<String, String>
+
+        wrapper.loadInitial(PageKeyedDataSource.LoadInitialParams<String>(4, true),
+                loadInitialCallback)
+        val expectedInitial = PAGE_MAP.get(INIT_KEY)!!
+        verify(loadInitialCallback).onResult(expectedInitial.data.map { it.toString() },
+                expectedInitial.prev, expectedInitial.next)
+        verifyNoMoreInteractions(loadInitialCallback)
+
+        @Suppress("UNCHECKED_CAST")
+        val loadCallback = mock(PageKeyedDataSource.LoadCallback::class.java)
+                as PageKeyedDataSource.LoadCallback<String, String>
+        // load after
+        wrapper.loadAfter(PageKeyedDataSource.LoadParams(expectedInitial.next!!, 4), loadCallback)
+        val expectedAfter = PAGE_MAP.get(expectedInitial.next)!!
+        verify(loadCallback).onResult(expectedAfter.data.map { it.toString() },
+                expectedAfter.next)
+        verifyNoMoreInteractions(loadCallback)
+
+        // load before
+        wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev!!, 4), loadCallback)
+        verify(loadCallback).onResult(expectedInitial.data.map { it.toString() },
+                expectedInitial.prev)
+        verifyNoMoreInteractions(loadCallback)
+    }
+
+    @Test
+    fun testManualWrappedDataSource() = verifyWrappedDataSource {
+        StringWrapperDataSource(it)
+    }
+
+    @Test
+    fun testListConverterWrappedDataSource() = verifyWrappedDataSource {
+        it.mapByPage { it.map { it.toString() } }
+    }
+    @Test
+    fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
+        it.map { it.toString() }
+    }
+
+    companion object {
+        // first load is 2nd page to ensure we test prepend as well as append behavior
+        private val INIT_KEY: String = "key 2"
+        private val PAGE_MAP: Map<String, Page>
+        private val ITEM_LIST: List<Item>
+
+        init {
+            val map = HashMap<String, Page>()
+            val list = ArrayList<Item>()
+            val pageCount = 5
+            for (i in 1..pageCount) {
+                val data = List(4) { Item("name $i $it") }
+                list.addAll(data)
+
+                val key = "key $i"
+                val prev = if (i > 1) ("key " + (i - 1)) else null
+                val next = if (i < pageCount) ("key " + (i + 1)) else null
+                map.put(key, Page(prev, data, next))
+            }
+            PAGE_MAP = map
+            ITEM_LIST = list
+        }
+    }
+
+    private fun drain() {
+        var executed: Boolean
+        do {
+            executed = mBackgroundThread.executeAll()
+            executed = mMainThread.executeAll() || executed
+        } while (executed)
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt b/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt
new file mode 100644
index 0000000..4ae9642
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt
@@ -0,0 +1,406 @@
+/*
+ * 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 androidx.paging
+
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@RunWith(JUnit4::class)
+class PagedStorageTest {
+    private fun createPage(vararg strings: String): List<String> {
+        return strings.asList()
+    }
+
+    @Test
+    fun construct() {
+        val storage = PagedStorage(2, createPage("a", "b"), 2)
+
+        assertArrayEquals(arrayOf(null, null, "a", "b", null, null), storage.toArray())
+        assertEquals(6, storage.size)
+    }
+
+    @Test
+    fun appendFill() {
+        val callback = mock(PagedStorage.Callback::class.java)
+
+        val storage = PagedStorage(2, createPage("a", "b"), 2)
+        storage.appendPage(createPage("c", "d"), callback)
+
+        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d"), storage.toArray())
+        verify(callback).onPageAppended(4, 2, 0)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun appendAdd() {
+        val callback = mock(PagedStorage.Callback::class.java)
+
+        val storage = PagedStorage(2, createPage("a", "b"), 0)
+        storage.appendPage(createPage("c", "d"), callback)
+
+        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d"), storage.toArray())
+        verify(callback).onPageAppended(4, 0, 2)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun appendFillAdd() {
+        val callback = mock(PagedStorage.Callback::class.java)
+
+        val storage = PagedStorage(2, createPage("a", "b"), 2)
+
+        // change 2 nulls into c, d
+        storage.appendPage(createPage("c", "d"), callback)
+
+        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d"), storage.toArray())
+        verify(callback).onPageAppended(4, 2, 0)
+        verifyNoMoreInteractions(callback)
+
+        // append e, f
+        storage.appendPage(createPage("e", "f"), callback)
+
+        assertArrayEquals(arrayOf(null, null, "a", "b", "c", "d", "e", "f"), storage.toArray())
+        verify(callback).onPageAppended(6, 0, 2)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun prependFill() {
+        val callback = mock(PagedStorage.Callback::class.java)
+
+        val storage = PagedStorage(2, createPage("c", "d"), 2)
+        storage.prependPage(createPage("a", "b"), callback)
+
+        assertArrayEquals(arrayOf("a", "b", "c", "d", null, null), storage.toArray())
+        verify(callback).onPagePrepended(0, 2, 0)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun prependAdd() {
+        val callback = mock(PagedStorage.Callback::class.java)
+
+        val storage = PagedStorage(0, createPage("c", "d"), 2)
+        storage.prependPage(createPage("a", "b"), callback)
+
+        assertArrayEquals(arrayOf("a", "b", "c", "d", null, null), storage.toArray())
+        verify(callback).onPagePrepended(0, 0, 2)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun prependFillAdd() {
+        val callback = mock(PagedStorage.Callback::class.java)
+
+        val storage = PagedStorage(2, createPage("e", "f"), 2)
+
+        // change 2 nulls into c, d
+        storage.prependPage(createPage("c", "d"), callback)
+
+        assertArrayEquals(arrayOf("c", "d", "e", "f", null, null), storage.toArray())
+        verify(callback).onPagePrepended(0, 2, 0)
+        verifyNoMoreInteractions(callback)
+
+        // prepend a, b
+        storage.prependPage(createPage("a", "b"), callback)
+
+        assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", null, null), storage.toArray())
+        verify(callback).onPagePrepended(0, 0, 2)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun isTiled_addend_smallerPageIsNotLast() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage(0, createPage("a", "a"), 0)
+        assertTrue(storage.isTiled)
+
+        storage.appendPage(createPage("a", "a"), callback)
+        assertTrue(storage.isTiled)
+
+        storage.appendPage(createPage("a"), callback)
+        assertTrue(storage.isTiled)
+
+        // no matter what we append here, we're no longer tiled
+        storage.appendPage(createPage("a", "a"), callback)
+        assertFalse(storage.isTiled)
+    }
+
+    @Test
+    fun isTiled_append_growingSizeDisable() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage(0, createPage("a", "a"), 0)
+        assertTrue(storage.isTiled)
+
+        // page size can't grow from append
+        storage.appendPage(createPage("a", "a", "a"), callback)
+        assertFalse(storage.isTiled)
+    }
+
+    @Test
+    fun isTiled_prepend_smallerPage() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage(0, createPage("a"), 0)
+        assertTrue(storage.isTiled)
+
+        storage.prependPage(createPage("a", "a"), callback)
+        assertTrue(storage.isTiled)
+
+        storage.prependPage(createPage("a", "a"), callback)
+        assertTrue(storage.isTiled)
+
+        storage.prependPage(createPage("a"), callback)
+        assertFalse(storage.isTiled)
+    }
+
+    @Test
+    fun isTiled_prepend_smallerThanInitialPage() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage(0, createPage("a", "a"), 0)
+        assertTrue(storage.isTiled)
+
+        storage.prependPage(createPage("a"), callback)
+        assertFalse(storage.isTiled)
+    }
+
+    @Test
+    fun get_tiled() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage(1, createPage("a", "b"), 5)
+        assertTrue(storage.isTiled)
+
+        storage.appendPage(createPage("c", "d"), callback)
+        storage.appendPage(createPage("e", "f"), callback)
+
+        assertTrue(storage.isTiled)
+        assertArrayEquals(arrayOf(null, "a", "b", "c", "d", "e", "f", null), storage.toArray())
+    }
+
+    @Test
+    fun get_nonTiled() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage(1, createPage("a"), 6)
+        assertTrue(storage.isTiled)
+
+        storage.appendPage(createPage("b", "c"), callback)
+        storage.appendPage(createPage("d", "e", "f"), callback)
+
+        assertFalse(storage.isTiled)
+        assertArrayEquals(arrayOf(null, "a", "b", "c", "d", "e", "f", null), storage.toArray())
+    }
+
+    @Test
+    fun insertOne() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(2, createPage("c", "d"), 3, 0, callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
+        verify(callback).onInitialized(7)
+        verifyNoMoreInteractions(callback)
+
+        storage.insertPage(4, createPage("e", "f"), callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf(null, null, "c", "d", "e", "f", null), storage.toArray())
+        verify(callback).onPageInserted(4, 2)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun insertThree() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(2, createPage("c", "d"), 3, 0, callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
+        verify(callback).onInitialized(7)
+        verifyNoMoreInteractions(callback)
+
+        // first, insert 1st page
+        storage.insertPage(0, createPage("a", "b"), callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf("a", "b", "c", "d", null, null, null), storage.toArray())
+        verify(callback).onPageInserted(0, 2)
+        verifyNoMoreInteractions(callback)
+
+        // then 3rd page
+        storage.insertPage(4, createPage("e", "f"), callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", null), storage.toArray())
+        verify(callback).onPageInserted(4, 2)
+        verifyNoMoreInteractions(callback)
+
+        // then last, small page
+        storage.insertPage(6, createPage("g"), callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf("a", "b", "c", "d", "e", "f", "g"), storage.toArray())
+        verify(callback).onPageInserted(6, 1)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun insertLastFirst() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(6, createPage("g"), 0, 0, callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf(null, null, null, null, null, null, "g"), storage.toArray())
+        verify(callback).onInitialized(7)
+        verifyNoMoreInteractions(callback)
+
+        // insert 1st page
+        storage.insertPage(0, createPage("a", "b"), callback)
+
+        assertEquals(7, storage.size)
+        assertArrayEquals(arrayOf("a", "b", null, null, null, null, "g"), storage.toArray())
+        verify(callback).onPageInserted(0, 2)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun insertFailure_decreaseLast() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(2, createPage("c", "d"), 0, 0, callback)
+
+        // should throw, page too small
+        storage.insertPage(0, createPage("a"), callback)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun insertFailure_increase() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(0, createPage("a", "b"), 3, 0, callback)
+
+        // should throw, page too big
+        storage.insertPage(2, createPage("c", "d", "e"), callback)
+    }
+
+    @Test
+    fun allocatePlaceholders_simple() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(2, createPage("c"), 2, 0, callback)
+
+        verify(callback).onInitialized(5)
+
+        storage.allocatePlaceholders(2, 1, 1, callback)
+
+        verify(callback).onPagePlaceholderInserted(1)
+        verify(callback).onPagePlaceholderInserted(3)
+        verifyNoMoreInteractions(callback)
+
+        assertArrayEquals(arrayOf(null, null, "c", null, null), storage.toArray())
+    }
+
+    @Test
+    fun allocatePlaceholders_adoptPageSize() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(4, createPage("e"), 0, 0, callback)
+
+        verify(callback).onInitialized(5)
+
+        storage.allocatePlaceholders(0, 2, 2, callback)
+
+        verify(callback).onPagePlaceholderInserted(0)
+        verify(callback).onPagePlaceholderInserted(1)
+        verifyNoMoreInteractions(callback)
+
+        assertArrayEquals(arrayOf(null, null, null, null, "e"), storage.toArray())
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun allocatePlaceholders_cannotShrinkPageSize() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(4, createPage("e", "f"), 0, 0, callback)
+
+        verify(callback).onInitialized(6)
+
+        storage.allocatePlaceholders(0, 2, 1, callback)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun allocatePlaceholders_cannotAdoptPageSize() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(2, createPage("c", "d"), 2, 0, callback)
+
+        verify(callback).onInitialized(6)
+
+        storage.allocatePlaceholders(0, 2, 3, callback)
+    }
+
+    @Test
+    fun get_placeholdersMulti() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(2, createPage("c", "d"), 3, 0, callback)
+
+        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
+
+        storage.allocatePlaceholders(0, 10, 2, callback)
+
+        // allocating placeholders shouldn't affect result of get
+        assertArrayEquals(arrayOf(null, null, "c", "d", null, null, null), storage.toArray())
+    }
+
+    @Test
+    fun hasPage() {
+        val callback = mock(PagedStorage.Callback::class.java)
+        val storage = PagedStorage<String>()
+
+        storage.init(4, createPage("e"), 0, 0, callback)
+
+        assertFalse(storage.hasPage(1, 0))
+        assertFalse(storage.hasPage(1, 1))
+        assertFalse(storage.hasPage(1, 2))
+        assertFalse(storage.hasPage(1, 3))
+        assertTrue(storage.hasPage(1, 4))
+
+        assertFalse(storage.hasPage(2, 0))
+        assertFalse(storage.hasPage(2, 1))
+        assertTrue(storage.hasPage(2, 2))
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
new file mode 100644
index 0000000..acf565b
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright 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 androidx.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@RunWith(JUnit4::class)
+class PositionalDataSourceTest {
+    private fun computeInitialLoadPos(
+            requestedStartPosition: Int,
+            requestedLoadSize: Int,
+            pageSize: Int,
+            totalCount: Int): Int {
+        val params = PositionalDataSource.LoadInitialParams(
+                requestedStartPosition, requestedLoadSize, pageSize, true)
+        return PositionalDataSource.computeInitialLoadPosition(params, totalCount)
+    }
+
+    @Test
+    fun computeInitialLoadPositionZero() {
+        assertEquals(0, computeInitialLoadPos(
+                requestedStartPosition = 0,
+                requestedLoadSize = 30,
+                pageSize = 10,
+                totalCount = 100))
+    }
+
+    @Test
+    fun computeInitialLoadPositionRequestedPositionIncluded() {
+        assertEquals(10, computeInitialLoadPos(
+                requestedStartPosition = 10,
+                requestedLoadSize = 10,
+                pageSize = 10,
+                totalCount = 100))
+    }
+
+    @Test
+    fun computeInitialLoadPositionRound() {
+        assertEquals(10, computeInitialLoadPos(
+                requestedStartPosition = 13,
+                requestedLoadSize = 30,
+                pageSize = 10,
+                totalCount = 100))
+    }
+
+    @Test
+    fun computeInitialLoadPositionEndAdjusted() {
+        assertEquals(70, computeInitialLoadPos(
+                requestedStartPosition = 99,
+                requestedLoadSize = 30,
+                pageSize = 10,
+                totalCount = 100))
+    }
+
+    @Test
+    fun computeInitialLoadPositionEndAdjustedAndAligned() {
+        assertEquals(70, computeInitialLoadPos(
+                requestedStartPosition = 99,
+                requestedLoadSize = 35,
+                pageSize = 10,
+                totalCount = 100))
+    }
+
+    @Test
+    fun fullLoadWrappedAsContiguous() {
+        // verify that prepend / append work correctly with a PositionalDataSource, made contiguous
+        val config = PagedList.Config.Builder()
+                .setPageSize(10)
+                .setInitialLoadSizeHint(10)
+                .setEnablePlaceholders(true)
+                .build()
+        val dataSource: PositionalDataSource<Int> = ListDataSource((0..99).toList())
+        val testExecutor = TestExecutor()
+        val pagedList = ContiguousPagedList(dataSource.wrapAsContiguousWithoutPlaceholders(),
+                testExecutor, testExecutor, null, config, 15,
+                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+
+        assertEquals((10..19).toList(), pagedList)
+
+        // prepend + append work correctly
+        pagedList.loadAround(5)
+        testExecutor.executeAll()
+        assertEquals((0..29).toList(), pagedList)
+
+        // and load the rest of the data to be sure further appends work
+        for (i in (2..9)) {
+            pagedList.loadAround(i * 10 - 5)
+            testExecutor.executeAll()
+            assertEquals((0..i * 10 + 9).toList(), pagedList)
+        }
+    }
+
+    private fun performLoadInitial(
+            enablePlaceholders: Boolean = true,
+            invalidateDataSource: Boolean = false,
+            callbackInvoker: (callback: PositionalDataSource.LoadInitialCallback<String>) -> Unit) {
+        val dataSource = object : PositionalDataSource<String>() {
+            override fun loadInitial(
+                    params: LoadInitialParams,
+                    callback: LoadInitialCallback<String>) {
+                if (invalidateDataSource) {
+                    // invalidate data source so it's invalid when onResult() called
+                    invalidate()
+                }
+                callbackInvoker(callback)
+            }
+
+            override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+                fail("loadRange not expected")
+            }
+        }
+
+        val config = PagedList.Config.Builder()
+                .setPageSize(10)
+                .setEnablePlaceholders(enablePlaceholders)
+                .build()
+        if (enablePlaceholders) {
+            TiledPagedList(dataSource, FailExecutor(), FailExecutor(), null, config, 0)
+        } else {
+            ContiguousPagedList(dataSource.wrapAsContiguousWithoutPlaceholders(),
+                    FailExecutor(), FailExecutor(), null, config, null,
+                    ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+        }
+    }
+
+    @Test
+    fun initialLoadCallbackSuccess() = performLoadInitial {
+        // LoadInitialCallback correct usage
+        it.onResult(listOf("a", "b"), 0, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackNotPageSizeMultiple() = performLoadInitial {
+        // Positional LoadInitialCallback can't accept result that's not a multiple of page size
+        val elevenLetterList = List(11) { "" + 'a' + it }
+        it.onResult(elevenLetterList, 0, 12)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackListTooBig() = performLoadInitial {
+        // LoadInitialCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b", "c"), 0, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackPositionTooLarge() = performLoadInitial {
+        // LoadInitialCallback can't accept pos + list > totalCount
+        it.onResult(listOf("a", "b"), 1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackPositionNegative() = performLoadInitial {
+        // LoadInitialCallback can't accept negative position
+        it.onResult(listOf("a", "b", "c"), -1, 2)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
+        // LoadInitialCallback can't accept empty result unless data set is empty
+        it.onResult(emptyList(), 0, 2)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun initialLoadCallbackRequireTotalCount() = performLoadInitial(enablePlaceholders = true) {
+        // LoadInitialCallback requires 3 args when placeholders enabled
+        it.onResult(listOf("a", "b"), 0)
+    }
+
+    @Test
+    fun initialLoadCallbackSuccessTwoArg() = performLoadInitial(enablePlaceholders = false) {
+        // LoadInitialCallback correct 2 arg usage
+        it.onResult(listOf("a", "b"), 0)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackPosNegativeTwoArg() = performLoadInitial(enablePlaceholders = false) {
+        // LoadInitialCallback can't accept negative position
+        it.onResult(listOf("a", "b"), -1)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialLoadCallbackEmptyWithOffset() = performLoadInitial(enablePlaceholders = false) {
+        // LoadInitialCallback can't accept empty result unless pos is 0
+        it.onResult(emptyList(), 1)
+    }
+
+    @Test
+    fun initialLoadCallbackInvalidTwoArg() = performLoadInitial(invalidateDataSource = true) {
+        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
+        it.onResult(emptyList(), 1)
+    }
+
+    @Test
+    fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
+        // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
+        it.onResult(emptyList(), 0, 1)
+    }
+
+    private abstract class WrapperDataSource<in A, B>(private val source: PositionalDataSource<A>)
+            : PositionalDataSource<B>() {
+        private val invalidatedCallback = DataSource.InvalidatedCallback {
+            invalidate()
+            removeCallback()
+        }
+
+        init {
+            source.addInvalidatedCallback(invalidatedCallback)
+        }
+
+        private fun removeCallback() {
+            removeInvalidatedCallback(invalidatedCallback)
+        }
+
+        override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<B>) {
+            source.loadInitial(params, object : LoadInitialCallback<A>() {
+                override fun onResult(data: List<A>, position: Int, totalCount: Int) {
+                    callback.onResult(convert(data), position, totalCount)
+                }
+
+                override fun onResult(data: List<A>, position: Int) {
+                    callback.onResult(convert(data), position)
+                }
+            })
+        }
+
+        override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<B>) {
+            source.loadRange(params, object : LoadRangeCallback<A>() {
+                override fun onResult(data: List<A>) {
+                    callback.onResult(convert(data))
+                }
+            })
+        }
+
+        protected abstract fun convert(source: List<A>): List<B>
+    }
+
+    private class StringWrapperDataSource<in A>(source: PositionalDataSource<A>)
+            : WrapperDataSource<A, String>(source) {
+        override fun convert(source: List<A>): List<String> {
+            return source.map { it.toString() }
+        }
+    }
+
+    private fun verifyWrappedDataSource(
+            createWrapper: (PositionalDataSource<Int>) -> PositionalDataSource<String>) {
+        val orig = ListDataSource(listOf(0, 5, 4, 8, 12))
+        val wrapper = createWrapper(orig)
+
+        // load initial
+        @Suppress("UNCHECKED_CAST")
+        val loadInitialCallback = mock(PositionalDataSource.LoadInitialCallback::class.java)
+                as PositionalDataSource.LoadInitialCallback<String>
+
+        wrapper.loadInitial(PositionalDataSource.LoadInitialParams(0, 2, 1, true),
+                loadInitialCallback)
+        verify(loadInitialCallback).onResult(listOf("0", "5"), 0, 5)
+        verifyNoMoreInteractions(loadInitialCallback)
+
+        // load range
+        @Suppress("UNCHECKED_CAST")
+        val loadRangeCallback = mock(PositionalDataSource.LoadRangeCallback::class.java)
+                as PositionalDataSource.LoadRangeCallback<String>
+
+        wrapper.loadRange(PositionalDataSource.LoadRangeParams(2, 3), loadRangeCallback)
+        verify(loadRangeCallback).onResult(listOf("4", "8", "12"))
+        verifyNoMoreInteractions(loadRangeCallback)
+
+        // check invalidation behavior
+        val invalCallback = mock(DataSource.InvalidatedCallback::class.java)
+        wrapper.addInvalidatedCallback(invalCallback)
+        orig.invalidate()
+        verify(invalCallback).onInvalidated()
+        verifyNoMoreInteractions(invalCallback)
+    }
+
+    @Test
+    fun testManualWrappedDataSource() = verifyWrappedDataSource {
+        StringWrapperDataSource(it)
+    }
+
+    @Test
+    fun testListConverterWrappedDataSource() = verifyWrappedDataSource {
+        it.mapByPage { it.map { it.toString() } }
+    }
+
+    @Test
+    fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
+        it.map { it.toString() }
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
new file mode 100644
index 0000000..5067e46
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 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 androidx.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import java.util.Collections
+
+@Suppress("DEPRECATION")
+@RunWith(JUnit4::class)
+class TiledDataSourceTest {
+
+    fun TiledDataSource<String>.loadInitial(
+            startPosition: Int, count: Int, pageSize: Int): List<String> {
+        @Suppress("UNCHECKED_CAST")
+        val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<String>
+
+        this.dispatchLoadInitial(true, startPosition, count, pageSize, FailExecutor(), receiver)
+
+        @Suppress("UNCHECKED_CAST")
+        val argument = ArgumentCaptor.forClass(PageResult::class.java)
+                as ArgumentCaptor<PageResult<String>>
+        verify(receiver).onPageResult(eq(PageResult.INIT), argument.capture())
+        verifyNoMoreInteractions(receiver)
+
+        val observed = argument.value
+
+        return observed.page
+    }
+
+    @Test
+    fun loadInitialEmpty() {
+        class EmptyDataSource : TiledDataSource<String>() {
+            override fun countItems(): Int {
+                return 0
+            }
+
+            override fun loadRange(startPosition: Int, count: Int): List<String> {
+                return emptyList()
+            }
+        }
+
+        assertEquals(Collections.EMPTY_LIST, EmptyDataSource().loadInitial(0, 1, 5))
+    }
+
+    @Test
+    fun loadInitialTooLong() {
+        val list = List(26) { "" + 'a' + it }
+        class AlphabetDataSource : TiledDataSource<String>() {
+            override fun countItems(): Int {
+                return list.size
+            }
+
+            override fun loadRange(startPosition: Int, count: Int): List<String> {
+                return list.subList(startPosition, startPosition + count)
+            }
+        }
+        // baseline behavior
+        assertEquals(list, AlphabetDataSource().loadInitial(0, 26, 10))
+        assertEquals(list, AlphabetDataSource().loadInitial(50, 26, 10))
+    }
+}
diff --git a/paging/common/src/test/java/androidx/paging/TiledPagedListTest.kt b/paging/common/src/test/java/androidx/paging/TiledPagedListTest.kt
new file mode 100644
index 0000000..2baf0aa
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/TiledPagedListTest.kt
@@ -0,0 +1,468 @@
+/*
+ * 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 androidx.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+
+@RunWith(JUnit4::class)
+class TiledPagedListTest {
+    private val mMainThread = TestExecutor()
+    private val mBackgroundThread = TestExecutor()
+
+    private class Item(position: Int) {
+        val name: String = "Item $position"
+
+        override fun toString(): String {
+            return name
+        }
+    }
+
+    private fun verifyLoadedPages(list: List<Item>, vararg loadedPages: Int) {
+        val loadedPageList = loadedPages.asList()
+        assertEquals(ITEMS.size, list.size)
+        for (i in list.indices) {
+            if (loadedPageList.contains(i / PAGE_SIZE)) {
+                assertSame("Index $i", ITEMS[i], list[i])
+            } else {
+                assertNull("Index $i", list[i])
+            }
+        }
+    }
+
+    private fun createTiledPagedList(loadPosition: Int, initPageCount: Int,
+            prefetchDistance: Int = PAGE_SIZE,
+            listData: List<Item> = ITEMS,
+            boundaryCallback: PagedList.BoundaryCallback<Item>? = null): TiledPagedList<Item> {
+        return TiledPagedList(
+                ListDataSource(listData), mMainThread, mBackgroundThread, boundaryCallback,
+                PagedList.Config.Builder()
+                        .setPageSize(PAGE_SIZE)
+                        .setInitialLoadSizeHint(PAGE_SIZE * initPageCount)
+                        .setPrefetchDistance(prefetchDistance)
+                        .build(),
+                loadPosition)
+    }
+
+    @Test
+    fun getDataSource() {
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
+        assertTrue(pagedList.dataSource is ListDataSource<Item>)
+
+        // snapshot keeps same DataSource
+        assertSame(pagedList.dataSource,
+                (pagedList.snapshot() as SnapshotPagedList<Item>).dataSource)
+    }
+
+    @Test
+    fun initialLoad_onePage() {
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
+        verifyLoadedPages(pagedList, 0, 1)
+    }
+
+    @Test
+    fun initialLoad_onePageOffset() {
+        val pagedList = createTiledPagedList(loadPosition = 10, initPageCount = 1)
+        verifyLoadedPages(pagedList, 0, 1)
+    }
+
+    @Test
+    fun initialLoad_full() {
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 100)
+        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
+    }
+
+    @Test
+    fun initialLoad_end() {
+        val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
+        verifyLoadedPages(pagedList, 3, 4)
+    }
+
+    @Test
+    fun initialLoad_multiple() {
+        val pagedList = createTiledPagedList(loadPosition = 9, initPageCount = 2)
+        verifyLoadedPages(pagedList, 0, 1)
+    }
+
+    @Test
+    fun initialLoad_offset() {
+        val pagedList = createTiledPagedList(loadPosition = 41, initPageCount = 2)
+        verifyLoadedPages(pagedList, 3, 4)
+    }
+
+    @Test
+    fun initialLoad_initializesLastKey() {
+        val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
+        assertEquals(44, pagedList.lastKey)
+    }
+
+    @Test
+    fun initialLoadAsync() {
+        val dataSource = AsyncListDataSource(ITEMS)
+        val pagedList = TiledPagedList(
+                dataSource, mMainThread, mBackgroundThread, null,
+                PagedList.Config.Builder().setPageSize(10).build(), 0)
+
+        assertTrue(pagedList.isEmpty())
+        drain()
+        assertTrue(pagedList.isEmpty())
+        dataSource.flush()
+        assertTrue(pagedList.isEmpty())
+        mBackgroundThread.executeAll()
+        assertTrue(pagedList.isEmpty())
+
+        // Data source defers callbacks until flush, which posts result to main thread
+        mMainThread.executeAll()
+        assertFalse(pagedList.isEmpty())
+    }
+
+    @Test
+    fun addWeakCallbackEmpty() {
+        val dataSource = AsyncListDataSource(ITEMS)
+        val pagedList = TiledPagedList(
+                dataSource, mMainThread, mBackgroundThread, null,
+                PagedList.Config.Builder().setPageSize(10).build(), 0)
+
+        // capture empty snapshot
+        val emptySnapshot = pagedList.snapshot()
+        assertTrue(pagedList.isEmpty())
+        assertTrue(emptySnapshot.isEmpty())
+
+        // data added in asynchronously
+        dataSource.flush()
+        drain()
+        assertFalse(pagedList.isEmpty())
+
+        // verify that adding callback works with empty start point
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(emptySnapshot, callback)
+        verify(callback).onInserted(0, pagedList.size)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun append() {
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyLoadedPages(pagedList, 0, 1)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(15)
+
+        verifyLoadedPages(pagedList, 0, 1)
+
+        drain()
+
+        verifyLoadedPages(pagedList, 0, 1, 2)
+        verify(callback).onChanged(20, 10)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun prepend() {
+        val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyLoadedPages(pagedList, 3, 4)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(35)
+        drain()
+
+        verifyLoadedPages(pagedList, 2, 3, 4)
+        verify<PagedList.Callback>(callback).onChanged(20, 10)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun loadWithGap() {
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyLoadedPages(pagedList, 0, 1)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(44)
+        drain()
+
+        verifyLoadedPages(pagedList, 0, 1, 3, 4)
+        verify(callback).onChanged(30, 10)
+        verify(callback).onChanged(40, 5)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun tinyPrefetchTest() {
+        val pagedList = createTiledPagedList(
+                loadPosition = 0, initPageCount = 1, prefetchDistance = 1)
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(null, callback)
+        verifyLoadedPages(pagedList, 0, 1)
+        verifyZeroInteractions(callback)
+
+        pagedList.loadAround(33)
+        drain()
+
+        verifyLoadedPages(pagedList, 0, 1, 3)
+        verify(callback).onChanged(30, 10)
+        verifyNoMoreInteractions(callback)
+
+        pagedList.loadAround(44)
+        drain()
+
+        verifyLoadedPages(pagedList, 0, 1, 3, 4)
+        verify(callback).onChanged(40, 5)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun appendCallbackAddedLate() {
+        val pagedList = createTiledPagedList(
+                loadPosition = 0, initPageCount = 1, prefetchDistance = 0)
+        verifyLoadedPages(pagedList, 0, 1)
+
+        pagedList.loadAround(25)
+        drain()
+        verifyLoadedPages(pagedList, 0, 1, 2)
+
+        // snapshot at 30 items
+        val snapshot = pagedList.snapshot()
+        verifyLoadedPages(snapshot, 0, 1, 2)
+
+        pagedList.loadAround(35)
+        pagedList.loadAround(44)
+        drain()
+        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
+        verifyLoadedPages(snapshot, 0, 1, 2)
+
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(snapshot, callback)
+        verify(callback).onChanged(30, 20)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun prependCallbackAddedLate() {
+        val pagedList = createTiledPagedList(
+                loadPosition = 44, initPageCount = 2, prefetchDistance = 0)
+        verifyLoadedPages(pagedList, 3, 4)
+
+        pagedList.loadAround(25)
+        drain()
+        verifyLoadedPages(pagedList, 2, 3, 4)
+
+        // snapshot at 30 items
+        val snapshot = pagedList.snapshot()
+        verifyLoadedPages(snapshot, 2, 3, 4)
+
+        pagedList.loadAround(15)
+        pagedList.loadAround(5)
+        drain()
+        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
+        verifyLoadedPages(snapshot, 2, 3, 4)
+
+        val callback = mock(PagedList.Callback::class.java)
+        pagedList.addWeakCallback(snapshot, callback)
+        verify(callback).onChanged(0, 20)
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun placeholdersDisabled() {
+        // disable placeholders with config, so we create a contiguous version of the pagedlist
+        val config = PagedList.Config.Builder()
+                .setPageSize(PAGE_SIZE)
+                .setPrefetchDistance(PAGE_SIZE)
+                .setInitialLoadSizeHint(PAGE_SIZE)
+                .setEnablePlaceholders(false)
+                .build()
+        val pagedList = PagedList.Builder<Int, Item>(ListDataSource(ITEMS), config)
+                .setMainThreadExecutor(mMainThread)
+                .setBackgroundThreadExecutor(mBackgroundThread)
+                .setInitialKey(20)
+                .build()
+
+        assertTrue(pagedList.isContiguous)
+
+        @Suppress("UNCHECKED_CAST")
+        val contiguousPagedList = pagedList as ContiguousPagedList<Int, Item>
+        assertEquals(0, contiguousPagedList.mStorage.leadingNullCount)
+        assertEquals(PAGE_SIZE, contiguousPagedList.mStorage.storageCount)
+        assertEquals(0, contiguousPagedList.mStorage.trailingNullCount)
+    }
+
+    @Test
+    fun boundaryCallback_empty() {
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1,
+                listData = ArrayList(), boundaryCallback = boundaryCallback)
+        assertEquals(0, pagedList.size)
+
+        // nothing yet
+        verifyNoMoreInteractions(boundaryCallback)
+
+        // onZeroItemsLoaded posted, since creation often happens on BG thread
+        drain()
+        verify(boundaryCallback).onZeroItemsLoaded()
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    @Test
+    fun boundaryCallback_immediate() {
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1,
+                listData = ITEMS.subList(0, 2), boundaryCallback = boundaryCallback)
+        assertEquals(2, pagedList.size)
+
+        // nothing yet
+        verifyZeroInteractions(boundaryCallback)
+
+        // callbacks posted, since creation often happens on BG thread
+        drain()
+        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS[0])
+        verify(boundaryCallback).onItemAtEndLoaded(ITEMS[1])
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    @Test
+    fun boundaryCallback_delayedUntilLoaded() {
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val pagedList = createTiledPagedList(loadPosition = 20, initPageCount = 1,
+                boundaryCallback = boundaryCallback)
+        verifyLoadedPages(pagedList, 1, 2) // 0, 3, and 4 not loaded yet
+
+        // nothing yet, even after drain
+        verifyZeroInteractions(boundaryCallback)
+        drain()
+        verifyZeroInteractions(boundaryCallback)
+
+        pagedList.loadAround(0)
+        pagedList.loadAround(44)
+
+        // still nothing, since items aren't loaded...
+        verifyZeroInteractions(boundaryCallback)
+
+        drain()
+        // first/last items loaded now, so callbacks dispatched
+        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
+        verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    @Test
+    fun boundaryCallback_delayedUntilNearbyAccess() {
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 5,
+                prefetchDistance = 2, boundaryCallback = boundaryCallback)
+        verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
+
+        // all items loaded, but no access near ends, so no callbacks
+        verifyZeroInteractions(boundaryCallback)
+        drain()
+        verifyZeroInteractions(boundaryCallback)
+
+        pagedList.loadAround(0)
+        pagedList.loadAround(44)
+
+        // callbacks not posted immediately
+        verifyZeroInteractions(boundaryCallback)
+
+        drain()
+
+        // items accessed, so now posted callbacks are run
+        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
+        verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    private fun validateCallbackForSize(initPageCount: Int, itemCount: Int) {
+        @Suppress("UNCHECKED_CAST")
+        val boundaryCallback =
+                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+        val listData = ITEMS.subList(0, itemCount)
+        val pagedList = createTiledPagedList(
+                loadPosition = 0,
+                initPageCount = initPageCount,
+                prefetchDistance = 0,
+                boundaryCallback = boundaryCallback,
+                listData = listData)
+        assertNotNull(pagedList[pagedList.size - 1 - PAGE_SIZE])
+        assertNull(pagedList.last()) // not completed loading
+
+        // no access near list beginning, so no callbacks yet
+        verifyNoMoreInteractions(boundaryCallback)
+        drain()
+        verifyNoMoreInteractions(boundaryCallback)
+
+        // trigger front boundary callback (via access)
+        pagedList.loadAround(0)
+        drain()
+        verify(boundaryCallback).onItemAtFrontLoaded(listData.first())
+        verifyNoMoreInteractions(boundaryCallback)
+
+        // trigger end boundary callback (via load)
+        pagedList.loadAround(pagedList.size - 1)
+        drain()
+        verify(boundaryCallback).onItemAtEndLoaded(listData.last())
+        verifyNoMoreInteractions(boundaryCallback)
+    }
+
+    @Test
+    fun boundaryCallbackPageSize1() {
+        // verify different alignments of last page still trigger boundaryCallback correctly
+        validateCallbackForSize(2, 3 * PAGE_SIZE - 2)
+        validateCallbackForSize(2, 3 * PAGE_SIZE - 1)
+        validateCallbackForSize(2, 3 * PAGE_SIZE)
+        validateCallbackForSize(3, 3 * PAGE_SIZE + 1)
+        validateCallbackForSize(3, 3 * PAGE_SIZE + 2)
+    }
+
+    private fun drain() {
+        var executed: Boolean
+        do {
+            executed = mBackgroundThread.executeAll()
+            executed = mMainThread.executeAll() || executed
+        } while (executed)
+    }
+
+    companion object {
+        // use a page size that's not an even divisor of ITEMS.size() to test end conditions
+        private val PAGE_SIZE = 10
+
+        private val ITEMS = List(45) { Item(it) }
+    }
+}
diff --git a/paging/integration-tests/testapp/build.gradle b/paging/integration-tests/testapp/build.gradle
index 9aab333..4264aef 100644
--- a/paging/integration-tests/testapp/build.gradle
+++ b/paging/integration-tests/testapp/build.gradle
@@ -22,16 +22,16 @@
 }
 
 dependencies {
-    implementation(project(":arch:runtime"))
-    implementation(project(":arch:common"))
-    implementation(project(":paging:common"))
-    implementation(project(":lifecycle:extensions"))
-    implementation(project(":lifecycle:runtime"))
-    implementation(project(":lifecycle:common"))
-    implementation(project(":paging:runtime"))
+    implementation(project(":arch:core-runtime"))
+    implementation(project(":arch:core-common"))
+    implementation(project(":paging:paging-common"))
+    implementation(project(":lifecycle:lifecycle-extensions"))
+    implementation(project(":lifecycle:lifecycle-runtime"))
+    implementation(project(":lifecycle:lifecycle-common"))
+    implementation(project(":paging:paging-runtime"))
     implementation(MULTIDEX)
-    implementation(SUPPORT_RECYCLERVIEW_27, libs.support_exclude_config)
-    implementation(SUPPORT_APPCOMPAT_27, libs.support_exclude_config)
+    implementation(SUPPORT_RECYCLERVIEW, libs.support_exclude_config)
+    implementation(SUPPORT_APPCOMPAT, libs.support_exclude_config)
 }
 
 tasks['check'].dependsOn(tasks['connectedCheck'])
diff --git a/paging/integration-tests/testapp/src/main/AndroidManifest.xml b/paging/integration-tests/testapp/src/main/AndroidManifest.xml
index 8ddf651..168c16f 100644
--- a/paging/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/paging/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -15,13 +15,13 @@
   ~ limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.paging.integration.testapp">
+          package="androidx.paging.integration.testapp">
     <application
         android:allowBackup="true"
         android:supportsRtl="true"
         android:theme="@style/Theme.AppCompat">
         <activity
-            android:name=".PagedListSampleActivity"
+            android:name="androidx.paging.integration.testapp.PagedListSampleActivity"
             android:label="PagedList Sample">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/Item.java b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/Item.java
deleted file mode 100644
index 94d25a8..0000000
--- a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/Item.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging.integration.testapp;
-
-import android.support.annotation.NonNull;
-import android.support.v7.util.DiffUtil;
-
-/**
- * Sample item.
- */
-class Item {
-    public final int id;
-    public final String text;
-    @SuppressWarnings("WeakerAccess")
-    public final int bgColor;
-
-    Item(int id, String text, int bgColor) {
-        this.id = id;
-        this.text = text;
-        this.bgColor = bgColor;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Item item = (Item) o;
-        return this.id == item.id
-                && this.bgColor == item.bgColor
-                && this.text.equals(item.text);
-    }
-
-    static final DiffUtil.ItemCallback<Item> DIFF_CALLBACK = new DiffUtil.ItemCallback<Item>() {
-        @Override
-        public boolean areContentsTheSame(@NonNull Item oldItem, @NonNull Item newItem) {
-            return oldItem.equals(newItem);
-        }
-
-        @Override
-        public boolean areItemsTheSame(@NonNull Item oldItem, @NonNull Item newItem) {
-            return oldItem.id == newItem.id;
-        }
-    };
-}
diff --git a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/ItemDataSource.java b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/ItemDataSource.java
deleted file mode 100644
index c53d361..0000000
--- a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/ItemDataSource.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging.integration.testapp;
-
-import android.arch.paging.PositionalDataSource;
-import android.graphics.Color;
-import android.support.annotation.ColorInt;
-import android.support.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Sample data source with artificial data.
- */
-class ItemDataSource extends PositionalDataSource<Item> {
-    private static final int COUNT = 500;
-
-    @ColorInt
-    private static final int[] COLORS = new int[] {
-            Color.RED,
-            Color.BLUE,
-            Color.BLACK,
-    };
-
-    private static int sGenerationId;
-    private final int mGenerationId = sGenerationId++;
-
-    private List<Item> loadRangeInternal(int startPosition, int loadCount) {
-        List<Item> items = new ArrayList<>();
-        int end = Math.min(COUNT, startPosition + loadCount);
-        int bgColor = COLORS[mGenerationId % COLORS.length];
-
-        try {
-            Thread.sleep(1000);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        for (int i = startPosition; i != end; i++) {
-            items.add(new Item(i, "item " + i, bgColor));
-        }
-        return items;
-    }
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams params,
-            @NonNull LoadInitialCallback<Item> callback) {
-        int position = computeInitialLoadPosition(params, COUNT);
-        int loadSize = computeInitialLoadSize(params, position, COUNT);
-        List<Item> data = loadRangeInternal(position, loadSize);
-        callback.onResult(data, position, COUNT);
-    }
-
-    @Override
-    public void loadRange(@NonNull LoadRangeParams params,
-            @NonNull LoadRangeCallback<Item> callback) {
-        List<Item> data = loadRangeInternal(params.startPosition, params.loadSize);
-        callback.onResult(data);
-    }
-}
diff --git a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemAdapter.java b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemAdapter.java
deleted file mode 100644
index 9432124..0000000
--- a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemAdapter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging.integration.testapp;
-
-import android.arch.paging.PagedListAdapter;
-import android.graphics.Color;
-import android.support.v7.widget.RecyclerView;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-/**
- * Sample PagedList item Adapter, which uses a AsyncPagedListDiffer.
- */
-class PagedListItemAdapter extends PagedListAdapter<Item, RecyclerView.ViewHolder> {
-
-    PagedListItemAdapter() {
-        super(Item.DIFF_CALLBACK);
-    }
-
-    @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(
-                new TextView(parent.getContext())) {};
-        holder.itemView.setMinimumHeight(400);
-        return holder;
-    }
-
-    @Override
-    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-        Item item = getItem(position);
-        if (item != null) {
-            ((TextView) (holder.itemView)).setText(item.text);
-            holder.itemView.setBackgroundColor(item.bgColor);
-        } else {
-            ((TextView) (holder.itemView)).setText(R.string.loading);
-            holder.itemView.setBackgroundColor(Color.TRANSPARENT);
-        }
-    }
-}
diff --git a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemViewModel.java b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemViewModel.java
deleted file mode 100644
index be14bc1..0000000
--- a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListItemViewModel.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging.integration.testapp;
-
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.ViewModel;
-import android.arch.paging.DataSource;
-import android.arch.paging.LivePagedListBuilder;
-import android.arch.paging.PagedList;
-
-/**
- * Sample ViewModel backed by an artificial data source
- */
-@SuppressWarnings("WeakerAccess")
-public class PagedListItemViewModel extends ViewModel {
-    private ItemDataSource mDataSource;
-    private final Object mDataSourceLock = new Object();
-
-    private final DataSource.Factory<Integer, Item> mFactory =
-            new DataSource.Factory<Integer, Item>() {
-        @Override
-        public DataSource<Integer, Item> create() {
-            ItemDataSource newDataSource = new ItemDataSource();
-            synchronized (mDataSourceLock) {
-                mDataSource = newDataSource;
-                return mDataSource;
-            }
-        }
-    };
-
-    private LiveData<PagedList<Item>> mLivePagedList =
-            new LivePagedListBuilder<>(mFactory, 20).build();
-
-    void invalidateList() {
-        synchronized (mDataSourceLock) {
-            if (mDataSource != null) {
-                mDataSource.invalidate();
-            }
-        }
-    }
-
-    LiveData<PagedList<Item>> getLivePagedList() {
-        return mLivePagedList;
-    }
-}
diff --git a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListSampleActivity.java b/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListSampleActivity.java
deleted file mode 100644
index f031cdb..0000000
--- a/paging/integration-tests/testapp/src/main/java/android/arch/paging/integration/testapp/PagedListSampleActivity.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging.integration.testapp;
-
-import android.arch.lifecycle.Observer;
-import android.arch.lifecycle.ViewModelProviders;
-import android.arch.paging.PagedList;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.widget.Button;
-
-/**
- * Sample PagedList activity with artificial data source.
- */
-public class PagedListSampleActivity extends AppCompatActivity {
-
-    @Override
-    protected void onCreate(final Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_recycler_view);
-        final PagedListItemViewModel viewModel = ViewModelProviders.of(this)
-                .get(PagedListItemViewModel.class);
-
-        final PagedListItemAdapter adapter = new PagedListItemAdapter();
-        final RecyclerView recyclerView = findViewById(R.id.recyclerview);
-        recyclerView.setAdapter(adapter);
-        viewModel.getLivePagedList().observe(this, new Observer<PagedList<Item>>() {
-            @Override
-            public void onChanged(@Nullable PagedList<Item> items) {
-                adapter.submitList(items);
-            }
-        });
-        final Button button = findViewById(R.id.button);
-        button.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                viewModel.invalidateList();
-            }
-        });
-    }
-}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/Item.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/Item.java
new file mode 100644
index 0000000..17cb348
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/Item.java
@@ -0,0 +1,59 @@
+/*
+ * 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 androidx.paging.integration.testapp;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+
+/**
+ * Sample item.
+ */
+class Item {
+    public final int id;
+    public final String text;
+    @SuppressWarnings("WeakerAccess")
+    public final int bgColor;
+
+    Item(int id, String text, int bgColor) {
+        this.id = id;
+        this.text = text;
+        this.bgColor = bgColor;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Item item = (Item) o;
+        return this.id == item.id
+                && this.bgColor == item.bgColor
+                && this.text.equals(item.text);
+    }
+
+    static final DiffUtil.ItemCallback<Item> DIFF_CALLBACK = new DiffUtil.ItemCallback<Item>() {
+        @Override
+        public boolean areContentsTheSame(@NonNull Item oldItem, @NonNull Item newItem) {
+            return oldItem.equals(newItem);
+        }
+
+        @Override
+        public boolean areItemsTheSame(@NonNull Item oldItem, @NonNull Item newItem) {
+            return oldItem.id == newItem.id;
+        }
+    };
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/ItemDataSource.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/ItemDataSource.java
new file mode 100644
index 0000000..064b05c
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/ItemDataSource.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 androidx.paging.integration.testapp;
+
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.paging.PositionalDataSource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Sample data source with artificial data.
+ */
+class ItemDataSource extends PositionalDataSource<Item> {
+    private static final int COUNT = 500;
+
+    @ColorInt
+    private static final int[] COLORS = new int[] {
+            Color.RED,
+            Color.BLUE,
+            Color.BLACK,
+    };
+
+    private static int sGenerationId;
+    private final int mGenerationId = sGenerationId++;
+
+    private List<Item> loadRangeInternal(int startPosition, int loadCount) {
+        List<Item> items = new ArrayList<>();
+        int end = Math.min(COUNT, startPosition + loadCount);
+        int bgColor = COLORS[mGenerationId % COLORS.length];
+
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        for (int i = startPosition; i != end; i++) {
+            items.add(new Item(i, "item " + i, bgColor));
+        }
+        return items;
+    }
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams params,
+            @NonNull LoadInitialCallback<Item> callback) {
+        int position = computeInitialLoadPosition(params, COUNT);
+        int loadSize = computeInitialLoadSize(params, position, COUNT);
+        List<Item> data = loadRangeInternal(position, loadSize);
+        callback.onResult(data, position, COUNT);
+    }
+
+    @Override
+    public void loadRange(@NonNull LoadRangeParams params,
+            @NonNull LoadRangeCallback<Item> callback) {
+        List<Item> data = loadRangeInternal(params.startPosition, params.loadSize);
+        callback.onResult(data);
+    }
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListItemAdapter.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListItemAdapter.java
new file mode 100644
index 0000000..0baa80c
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListItemAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging.integration.testapp;
+
+import android.graphics.Color;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.paging.PagedListAdapter;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Sample PagedList item Adapter, which uses a AsyncPagedListDiffer.
+ */
+class PagedListItemAdapter extends PagedListAdapter<Item, RecyclerView.ViewHolder> {
+
+    PagedListItemAdapter() {
+        super(Item.DIFF_CALLBACK);
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(
+                new TextView(parent.getContext())) {};
+        holder.itemView.setMinimumHeight(400);
+        return holder;
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        Item item = getItem(position);
+        if (item != null) {
+            ((TextView) (holder.itemView)).setText(item.text);
+            holder.itemView.setBackgroundColor(item.bgColor);
+        } else {
+            ((TextView) (holder.itemView)).setText(R.string.loading);
+            holder.itemView.setBackgroundColor(Color.TRANSPARENT);
+        }
+    }
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListItemViewModel.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListItemViewModel.java
new file mode 100644
index 0000000..08f3af2
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListItemViewModel.java
@@ -0,0 +1,59 @@
+/*
+ * 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 androidx.paging.integration.testapp;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.ViewModel;
+import androidx.paging.DataSource;
+import androidx.paging.LivePagedListBuilder;
+import androidx.paging.PagedList;
+
+/**
+ * Sample ViewModel backed by an artificial data source
+ */
+@SuppressWarnings("WeakerAccess")
+public class PagedListItemViewModel extends ViewModel {
+    private ItemDataSource mDataSource;
+    private final Object mDataSourceLock = new Object();
+
+    private final DataSource.Factory<Integer, Item> mFactory =
+            new DataSource.Factory<Integer, Item>() {
+        @Override
+        public DataSource<Integer, Item> create() {
+            ItemDataSource newDataSource = new ItemDataSource();
+            synchronized (mDataSourceLock) {
+                mDataSource = newDataSource;
+                return mDataSource;
+            }
+        }
+    };
+
+    private LiveData<PagedList<Item>> mLivePagedList =
+            new LivePagedListBuilder<>(mFactory, 20).build();
+
+    void invalidateList() {
+        synchronized (mDataSourceLock) {
+            if (mDataSource != null) {
+                mDataSource.invalidate();
+            }
+        }
+    }
+
+    LiveData<PagedList<Item>> getLivePagedList() {
+        return mLivePagedList;
+    }
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListSampleActivity.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListSampleActivity.java
new file mode 100644
index 0000000..4eabbdc
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/PagedListSampleActivity.java
@@ -0,0 +1,59 @@
+/*
+ * 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 androidx.paging.integration.testapp;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.paging.PagedList;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Sample PagedList activity with artificial data source.
+ */
+public class PagedListSampleActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_recycler_view);
+        final PagedListItemViewModel viewModel = ViewModelProviders.of(this)
+                .get(PagedListItemViewModel.class);
+
+        final PagedListItemAdapter adapter = new PagedListItemAdapter();
+        final RecyclerView recyclerView = findViewById(R.id.recyclerview);
+        recyclerView.setAdapter(adapter);
+        viewModel.getLivePagedList().observe(this, new Observer<PagedList<Item>>() {
+            @Override
+            public void onChanged(@Nullable PagedList<Item> items) {
+                adapter.submitList(items);
+            }
+        });
+        final Button button = findViewById(R.id.button);
+        button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                viewModel.invalidateList();
+            }
+        });
+    }
+}
diff --git a/paging/runtime/build.gradle b/paging/runtime/build.gradle
index 20a39d4..dd9189c 100644
--- a/paging/runtime/build.gradle
+++ b/paging/runtime/build.gradle
@@ -25,12 +25,12 @@
 }
 
 dependencies {
-    api(project(":arch:runtime"))
-    api(project(":paging:common"))
-    api(project(":lifecycle:runtime"))
-    api(project(":lifecycle:livedata"))
+    api(project(":arch:core-runtime"))
+    api(project(":paging:paging-common"))
+    api(project(":lifecycle:lifecycle-runtime"))
+    api(project(":lifecycle:lifecycle-livedata"))
 
-    api(SUPPORT_RECYCLERVIEW_27, libs.support_exclude_config)
+    api(SUPPORT_RECYCLERVIEW, libs.support_exclude_config)
 
     androidTestImplementation(JUNIT)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/AsyncPagedListDifferTest.kt b/paging/runtime/src/androidTest/java/android/arch/paging/AsyncPagedListDifferTest.kt
deleted file mode 100644
index 586d762..0000000
--- a/paging/runtime/src/androidTest/java/android/arch/paging/AsyncPagedListDifferTest.kt
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import android.arch.core.executor.ArchTaskExecutor
-import android.support.test.filters.SmallTest
-import android.support.v7.recyclerview.extensions.AsyncDifferConfig
-import android.support.v7.util.DiffUtil
-import android.support.v7.util.ListUpdateCallback
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
-
-@SmallTest
-@RunWith(JUnit4::class)
-class AsyncPagedListDifferTest {
-    private val mMainThread = TestExecutor()
-    private val mDiffThread = TestExecutor()
-    private val mPageLoadingThread = TestExecutor()
-
-    private fun <T> createDiffer(listUpdateCallback: ListUpdateCallback,
-                                 diffCallback: DiffUtil.ItemCallback<T>): AsyncPagedListDiffer<T> {
-        val differ = AsyncPagedListDiffer(listUpdateCallback,
-                AsyncDifferConfig.Builder<T>(diffCallback)
-                        .setBackgroundThreadExecutor(mDiffThread)
-                        .build())
-        // by default, use ArchExecutor
-        assertEquals(differ.mMainThreadExecutor, ArchTaskExecutor.getMainThreadExecutor())
-        differ.mMainThreadExecutor = mMainThread
-        return differ
-    }
-
-    private fun <V> createPagedListFromListAndPos(
-            config: PagedList.Config, data: List<V>, initialKey: Int): PagedList<V> {
-        return PagedList.Builder<Int, V>(ListDataSource(data), config)
-                .setInitialKey(initialKey)
-                .setMainThreadExecutor(mMainThread)
-                .setBackgroundThreadExecutor(mPageLoadingThread)
-                .build()
-    }
-
-    @Test
-    fun initialState() {
-        val callback = mock(ListUpdateCallback::class.java)
-        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
-        assertEquals(null, differ.currentList)
-        assertEquals(0, differ.itemCount)
-        verifyZeroInteractions(callback)
-    }
-
-    @Test
-    fun setFullList() {
-        val callback = mock(ListUpdateCallback::class.java)
-        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
-        differ.submitList(StringPagedList(0, 0, "a", "b"))
-
-        assertEquals(2, differ.itemCount)
-        assertEquals("a", differ.getItem(0))
-        assertEquals("b", differ.getItem(1))
-
-        verify(callback).onInserted(0, 2)
-        verifyNoMoreInteractions(callback)
-        drain()
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test(expected = IndexOutOfBoundsException::class)
-    fun getEmpty() {
-        val differ = createDiffer(IGNORE_CALLBACK, STRING_DIFF_CALLBACK)
-        differ.getItem(0)
-    }
-
-    @Test(expected = IndexOutOfBoundsException::class)
-    fun getNegative() {
-        val differ = createDiffer(IGNORE_CALLBACK, STRING_DIFF_CALLBACK)
-        differ.submitList(StringPagedList(0, 0, "a", "b"))
-        differ.getItem(-1)
-    }
-
-    @Test(expected = IndexOutOfBoundsException::class)
-    fun getPastEnd() {
-        val differ = createDiffer(IGNORE_CALLBACK, STRING_DIFF_CALLBACK)
-        differ.submitList(StringPagedList(0, 0, "a", "b"))
-        differ.getItem(2)
-    }
-
-    @Test
-    fun simpleStatic() {
-        val callback = mock(ListUpdateCallback::class.java)
-        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
-
-        assertEquals(0, differ.itemCount)
-
-        differ.submitList(StringPagedList(2, 2, "a", "b"))
-
-        verify(callback).onInserted(0, 6)
-        verifyNoMoreInteractions(callback)
-        assertEquals(6, differ.itemCount)
-
-        assertNull(differ.getItem(0))
-        assertNull(differ.getItem(1))
-        assertEquals("a", differ.getItem(2))
-        assertEquals("b", differ.getItem(3))
-        assertNull(differ.getItem(4))
-        assertNull(differ.getItem(5))
-    }
-
-    @Test
-    fun pagingInContent() {
-        val config = PagedList.Config.Builder()
-                .setInitialLoadSizeHint(4)
-                .setPageSize(2)
-                .setPrefetchDistance(2)
-                .build()
-
-        val callback = mock(ListUpdateCallback::class.java)
-        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
-
-        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2))
-        verify(callback).onInserted(0, ALPHABET_LIST.size)
-        verifyNoMoreInteractions(callback)
-        drain()
-        verifyNoMoreInteractions(callback)
-
-        // get without triggering prefetch...
-        differ.getItem(1)
-        verifyNoMoreInteractions(callback)
-        drain()
-        verifyNoMoreInteractions(callback)
-
-        // get triggering prefetch...
-        differ.getItem(2)
-        verifyNoMoreInteractions(callback)
-        drain()
-        verify(callback).onChanged(4, 2, null)
-        verifyNoMoreInteractions(callback)
-
-        // get with no data loaded nearby...
-        differ.getItem(12)
-        verifyNoMoreInteractions(callback)
-        drain()
-        verify(callback).onChanged(10, 2, null)
-        verify(callback).onChanged(12, 2, null)
-        verify(callback).onChanged(14, 2, null)
-        verifyNoMoreInteractions(callback)
-
-        // finally, clear
-        differ.submitList(null)
-        verify(callback).onRemoved(0, 26)
-        drain()
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun simpleSwap() {
-        // Page size large enough to load
-        val config = PagedList.Config.Builder()
-                .setPageSize(50)
-                .build()
-
-        val callback = mock(ListUpdateCallback::class.java)
-        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
-
-        // initial list missing one item (immediate)
-        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 25), 0))
-        verify(callback).onInserted(0, 25)
-        verifyNoMoreInteractions(callback)
-        assertEquals(differ.itemCount, 25)
-        drain()
-        verifyNoMoreInteractions(callback)
-
-        // pass second list with full data
-        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 0))
-        verifyNoMoreInteractions(callback)
-        drain()
-        verify(callback).onInserted(25, 1)
-        verifyNoMoreInteractions(callback)
-        assertEquals(differ.itemCount, 26)
-
-        // finally, clear (immediate)
-        differ.submitList(null)
-        verify(callback).onRemoved(0, 26)
-        verifyNoMoreInteractions(callback)
-        drain()
-        verifyNoMoreInteractions(callback)
-    }
-
-    @Test
-    fun newPageWhileDiffing() {
-        val config = PagedList.Config.Builder()
-                .setInitialLoadSizeHint(4)
-                .setPageSize(2)
-                .setPrefetchDistance(2)
-                .build()
-
-        val callback = mock(ListUpdateCallback::class.java)
-        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
-
-        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2))
-        verify(callback).onInserted(0, ALPHABET_LIST.size)
-        verifyNoMoreInteractions(callback)
-        drain()
-        verifyNoMoreInteractions(callback)
-        assertNotNull(differ.currentList)
-        assertFalse(differ.currentList!!.isImmutable)
-
-        // trigger page loading
-        differ.getItem(10)
-        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2))
-        verifyNoMoreInteractions(callback)
-
-        // drain page fetching, but list became immutable, page will be ignored
-        drainExceptDiffThread()
-        verifyNoMoreInteractions(callback)
-        assertNotNull(differ.currentList)
-        assertTrue(differ.currentList!!.isImmutable)
-
-        // finally full drain, which signals nothing, since 1st pagedlist == 2nd pagedlist
-        drain()
-        verifyNoMoreInteractions(callback)
-        assertNotNull(differ.currentList)
-        assertFalse(differ.currentList!!.isImmutable)
-    }
-
-    @Test
-    fun itemCountUpdatedBeforeListUpdateCallbacks() {
-        // verify that itemCount is updated in the differ before dispatching ListUpdateCallbacks
-
-        val expectedCount = intArrayOf(0)
-        // provides access to differ, which must be constructed after callback
-        val differAccessor = arrayOf<AsyncPagedListDiffer<*>?>(null)
-
-        val callback = object : ListUpdateCallback {
-            override fun onInserted(position: Int, count: Int) {
-                assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
-            }
-
-            override fun onRemoved(position: Int, count: Int) {
-                assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
-            }
-
-            override fun onMoved(fromPosition: Int, toPosition: Int) {
-                fail("not expected")
-            }
-
-            override fun onChanged(position: Int, count: Int, payload: Any) {
-                fail("not expected")
-            }
-        }
-
-        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
-        differAccessor[0] = differ
-
-        val config = PagedList.Config.Builder()
-                .setPageSize(20)
-                .build()
-
-        // in the fast-add case...
-        expectedCount[0] = 5
-        assertEquals(0, differ.itemCount)
-        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 5), 0))
-        assertEquals(5, differ.itemCount)
-
-        // in the slow, diff on BG thread case...
-        expectedCount[0] = 10
-        assertEquals(5, differ.itemCount)
-        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 10), 0))
-        drain()
-        assertEquals(10, differ.itemCount)
-
-        // and in the fast-remove case
-        expectedCount[0] = 0
-        assertEquals(10, differ.itemCount)
-        differ.submitList(null)
-        assertEquals(0, differ.itemCount)
-    }
-
-    private fun drainExceptDiffThread() {
-        var executed: Boolean
-        do {
-            executed = mPageLoadingThread.executeAll()
-            executed = mMainThread.executeAll() or executed
-        } while (executed)
-    }
-
-    private fun drain() {
-        var executed: Boolean
-        do {
-            executed = mPageLoadingThread.executeAll()
-            executed = mDiffThread.executeAll() or executed
-            executed = mMainThread.executeAll() or executed
-        } while (executed)
-    }
-
-    companion object {
-        private val ALPHABET_LIST = List(26) { "" + 'a' + it }
-
-        private val STRING_DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
-            override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
-                return oldItem == newItem
-            }
-
-            override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
-                return oldItem == newItem
-            }
-        }
-
-        private val IGNORE_CALLBACK = object : ListUpdateCallback {
-            override fun onInserted(position: Int, count: Int) {}
-
-            override fun onRemoved(position: Int, count: Int) {}
-
-            override fun onMoved(fromPosition: Int, toPosition: Int) {}
-
-            override fun onChanged(position: Int, count: Int, payload: Any) {}
-        }
-    }
-}
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/PagedStorageDiffHelperTest.kt b/paging/runtime/src/androidTest/java/android/arch/paging/PagedStorageDiffHelperTest.kt
deleted file mode 100644
index 8b885f2..0000000
--- a/paging/runtime/src/androidTest/java/android/arch/paging/PagedStorageDiffHelperTest.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import android.support.test.filters.SmallTest
-import android.support.v7.util.DiffUtil
-import android.support.v7.util.ListUpdateCallback
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
-
-@SmallTest
-@RunWith(JUnit4::class)
-class PagedStorageDiffHelperTest {
-
-    @Test
-    fun sameListNoUpdates() {
-        validateTwoListDiff(
-                PagedStorage(5, listOf("a", "b", "c"), 5),
-                PagedStorage(5, listOf("a", "b", "c"), 5)) {
-            verifyZeroInteractions(it)
-        }
-    }
-
-    @Test
-    fun sameListNoUpdatesPlaceholder() {
-        val storageNoPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
-
-        val storageWithPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
-        storageWithPlaceholder.allocatePlaceholders(3, 0, 3,
-                /* ignored */ mock(PagedStorage.Callback::class.java))
-
-        // even though one has placeholders, and null counts are different...
-        assertEquals(10, storageNoPlaceholder.trailingNullCount)
-        assertEquals(7, storageWithPlaceholder.trailingNullCount)
-
-        // ... should be no interactions, since content still same
-        validateTwoListDiff(
-                storageNoPlaceholder,
-                storageWithPlaceholder) {
-            verifyZeroInteractions(it)
-        }
-    }
-
-    @Test
-    fun appendFill() {
-        validateTwoListDiff(
-                PagedStorage(5, listOf("a", "b"), 5),
-                PagedStorage(5, listOf("a", "b", "c"), 4)) {
-            verify(it).onRemoved(11, 1)
-            verify(it).onInserted(7, 1)
-            // NOTE: ideally would be onChanged(7, 1, null)
-            verifyNoMoreInteractions(it)
-        }
-    }
-
-    @Test
-    fun prependFill() {
-        validateTwoListDiff(
-                PagedStorage(5, listOf("b", "c"), 5),
-                PagedStorage(4, listOf("a", "b", "c"), 5)) {
-            verify(it).onRemoved(0, 1)
-            verify(it).onInserted(4, 1)
-            //NOTE: ideally would be onChanged(4, 1, null);
-            verifyNoMoreInteractions(it)
-        }
-    }
-
-    @Test
-    fun change() {
-        validateTwoListDiff(
-                PagedStorage(5, listOf("a1", "b1", "c1"), 5),
-                PagedStorage(5, listOf("a2", "b1", "c2"), 5)) {
-            verify(it).onChanged(5, 1, null)
-            verify(it).onChanged(7, 1, null)
-            verifyNoMoreInteractions(it)
-        }
-    }
-
-    companion object {
-        private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
-            override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
-                // first char means same item
-                return oldItem[0] == newItem[0]
-            }
-
-            override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
-                return oldItem == newItem
-            }
-        }
-
-        private fun validateTwoListDiff(oldList: PagedStorage<String>,
-                                        newList: PagedStorage<String>,
-                                        validator: (callback: ListUpdateCallback) -> Unit) {
-            val diffResult = PagedStorageDiffHelper.computeDiff(oldList, newList, DIFF_CALLBACK)
-
-            val listUpdateCallback = mock(ListUpdateCallback::class.java)
-            PagedStorageDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult)
-
-            validator(listUpdateCallback)
-        }
-    }
-}
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/android/arch/paging/StringPagedList.kt
deleted file mode 100644
index 00e8b0e..0000000
--- a/paging/runtime/src/androidTest/java/android/arch/paging/StringPagedList.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-class StringPagedList constructor(leadingNulls: Int, trailingNulls: Int, vararg items: String)
-        : PagedList<String>(PagedStorage<String>(), TestExecutor(), TestExecutor(), null,
-                PagedList.Config.Builder().setPageSize(1).build()), PagedStorage.Callback {
-    init {
-        @Suppress("UNCHECKED_CAST")
-        val keyedStorage = mStorage as PagedStorage<String>
-        keyedStorage.init(leadingNulls,
-                items.toList(),
-                trailingNulls,
-                0,
-                this)
-    }
-
-    internal override fun isContiguous(): Boolean {
-        return true
-    }
-
-    override fun getLastKey(): Any? {
-        return null
-    }
-
-    override fun dispatchUpdatesSinceSnapshot(storageSnapshot: PagedList<String>,
-            callback: PagedList.Callback) {
-    }
-
-    override fun loadAroundInternal(index: Int) {}
-
-    override fun onInitialized(count: Int) {}
-
-    override fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int) {}
-
-    override fun onPageAppended(endPosition: Int, changed: Int, added: Int) {}
-
-    override fun onPagePlaceholderInserted(pageIndex: Int) {}
-
-    override fun onPageInserted(start: Int, count: Int) {}
-
-    override fun getDataSource(): DataSource<*, String> {
-        throw UnsupportedOperationException()
-    }
-}
diff --git a/paging/runtime/src/androidTest/java/android/arch/paging/TestExecutor.kt b/paging/runtime/src/androidTest/java/android/arch/paging/TestExecutor.kt
deleted file mode 100644
index 34e9787..0000000
--- a/paging/runtime/src/androidTest/java/android/arch/paging/TestExecutor.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging
-
-import java.util.LinkedList
-import java.util.concurrent.Executor
-
-class TestExecutor : Executor {
-    private val mTasks = LinkedList<Runnable>()
-
-    override fun execute(command: Runnable) {
-        mTasks.add(command)
-    }
-
-    fun executeAll(): Boolean {
-        val consumed = !mTasks.isEmpty()
-
-        var task = mTasks.poll()
-        while (task != null) {
-            task.run()
-            task = mTasks.poll()
-        }
-        return consumed
-    }
-}
\ No newline at end of file
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
new file mode 100644
index 0000000..169779d
--- /dev/null
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
@@ -0,0 +1,342 @@
+/*
+ * 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 androidx.paging
+
+import android.support.test.filters.SmallTest
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListUpdateCallback
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AsyncPagedListDifferTest {
+    private val mMainThread = TestExecutor()
+    private val mDiffThread = TestExecutor()
+    private val mPageLoadingThread = TestExecutor()
+
+    private fun <T> createDiffer(listUpdateCallback: ListUpdateCallback,
+                                 diffCallback: DiffUtil.ItemCallback<T>): AsyncPagedListDiffer<T> {
+        val differ = AsyncPagedListDiffer(listUpdateCallback,
+                AsyncDifferConfig.Builder<T>(diffCallback)
+                        .setBackgroundThreadExecutor(mDiffThread)
+                        .build())
+        // by default, use ArchExecutor
+        assertEquals(differ.mMainThreadExecutor, ArchTaskExecutor.getMainThreadExecutor())
+        differ.mMainThreadExecutor = mMainThread
+        return differ
+    }
+
+    private fun <V> createPagedListFromListAndPos(
+            config: PagedList.Config, data: List<V>, initialKey: Int): PagedList<V> {
+        return PagedList.Builder<Int, V>(ListDataSource(data), config)
+                .setInitialKey(initialKey)
+                .setMainThreadExecutor(mMainThread)
+                .setBackgroundThreadExecutor(mPageLoadingThread)
+                .build()
+    }
+
+    @Test
+    fun initialState() {
+        val callback = mock(ListUpdateCallback::class.java)
+        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
+        assertEquals(null, differ.currentList)
+        assertEquals(0, differ.itemCount)
+        verifyZeroInteractions(callback)
+    }
+
+    @Test
+    fun setFullList() {
+        val callback = mock(ListUpdateCallback::class.java)
+        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
+        differ.submitList(StringPagedList(0, 0, "a", "b"))
+
+        assertEquals(2, differ.itemCount)
+        assertEquals("a", differ.getItem(0))
+        assertEquals("b", differ.getItem(1))
+
+        verify(callback).onInserted(0, 2)
+        verifyNoMoreInteractions(callback)
+        drain()
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test(expected = IndexOutOfBoundsException::class)
+    fun getEmpty() {
+        val differ = createDiffer(IGNORE_CALLBACK, STRING_DIFF_CALLBACK)
+        differ.getItem(0)
+    }
+
+    @Test(expected = IndexOutOfBoundsException::class)
+    fun getNegative() {
+        val differ = createDiffer(IGNORE_CALLBACK, STRING_DIFF_CALLBACK)
+        differ.submitList(StringPagedList(0, 0, "a", "b"))
+        differ.getItem(-1)
+    }
+
+    @Test(expected = IndexOutOfBoundsException::class)
+    fun getPastEnd() {
+        val differ = createDiffer(IGNORE_CALLBACK, STRING_DIFF_CALLBACK)
+        differ.submitList(StringPagedList(0, 0, "a", "b"))
+        differ.getItem(2)
+    }
+
+    @Test
+    fun simpleStatic() {
+        val callback = mock(ListUpdateCallback::class.java)
+        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
+
+        assertEquals(0, differ.itemCount)
+
+        differ.submitList(StringPagedList(2, 2, "a", "b"))
+
+        verify(callback).onInserted(0, 6)
+        verifyNoMoreInteractions(callback)
+        assertEquals(6, differ.itemCount)
+
+        assertNull(differ.getItem(0))
+        assertNull(differ.getItem(1))
+        assertEquals("a", differ.getItem(2))
+        assertEquals("b", differ.getItem(3))
+        assertNull(differ.getItem(4))
+        assertNull(differ.getItem(5))
+    }
+
+    @Test
+    fun pagingInContent() {
+        val config = PagedList.Config.Builder()
+                .setInitialLoadSizeHint(4)
+                .setPageSize(2)
+                .setPrefetchDistance(2)
+                .build()
+
+        val callback = mock(ListUpdateCallback::class.java)
+        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
+
+        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2))
+        verify(callback).onInserted(0, ALPHABET_LIST.size)
+        verifyNoMoreInteractions(callback)
+        drain()
+        verifyNoMoreInteractions(callback)
+
+        // get without triggering prefetch...
+        differ.getItem(1)
+        verifyNoMoreInteractions(callback)
+        drain()
+        verifyNoMoreInteractions(callback)
+
+        // get triggering prefetch...
+        differ.getItem(2)
+        verifyNoMoreInteractions(callback)
+        drain()
+        verify(callback).onChanged(4, 2, null)
+        verifyNoMoreInteractions(callback)
+
+        // get with no data loaded nearby...
+        differ.getItem(12)
+        verifyNoMoreInteractions(callback)
+        drain()
+        verify(callback).onChanged(10, 2, null)
+        verify(callback).onChanged(12, 2, null)
+        verify(callback).onChanged(14, 2, null)
+        verifyNoMoreInteractions(callback)
+
+        // finally, clear
+        differ.submitList(null)
+        verify(callback).onRemoved(0, 26)
+        drain()
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun simpleSwap() {
+        // Page size large enough to load
+        val config = PagedList.Config.Builder()
+                .setPageSize(50)
+                .build()
+
+        val callback = mock(ListUpdateCallback::class.java)
+        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
+
+        // initial list missing one item (immediate)
+        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 25), 0))
+        verify(callback).onInserted(0, 25)
+        verifyNoMoreInteractions(callback)
+        assertEquals(differ.itemCount, 25)
+        drain()
+        verifyNoMoreInteractions(callback)
+
+        // pass second list with full data
+        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 0))
+        verifyNoMoreInteractions(callback)
+        drain()
+        verify(callback).onInserted(25, 1)
+        verifyNoMoreInteractions(callback)
+        assertEquals(differ.itemCount, 26)
+
+        // finally, clear (immediate)
+        differ.submitList(null)
+        verify(callback).onRemoved(0, 26)
+        verifyNoMoreInteractions(callback)
+        drain()
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun newPageWhileDiffing() {
+        val config = PagedList.Config.Builder()
+                .setInitialLoadSizeHint(4)
+                .setPageSize(2)
+                .setPrefetchDistance(2)
+                .build()
+
+        val callback = mock(ListUpdateCallback::class.java)
+        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
+
+        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2))
+        verify(callback).onInserted(0, ALPHABET_LIST.size)
+        verifyNoMoreInteractions(callback)
+        drain()
+        verifyNoMoreInteractions(callback)
+        assertNotNull(differ.currentList)
+        assertFalse(differ.currentList!!.isImmutable)
+
+        // trigger page loading
+        differ.getItem(10)
+        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST, 2))
+        verifyNoMoreInteractions(callback)
+
+        // drain page fetching, but list became immutable, page will be ignored
+        drainExceptDiffThread()
+        verifyNoMoreInteractions(callback)
+        assertNotNull(differ.currentList)
+        assertTrue(differ.currentList!!.isImmutable)
+
+        // finally full drain, which signals nothing, since 1st pagedlist == 2nd pagedlist
+        drain()
+        verifyNoMoreInteractions(callback)
+        assertNotNull(differ.currentList)
+        assertFalse(differ.currentList!!.isImmutable)
+    }
+
+    @Test
+    fun itemCountUpdatedBeforeListUpdateCallbacks() {
+        // verify that itemCount is updated in the differ before dispatching ListUpdateCallbacks
+
+        val expectedCount = intArrayOf(0)
+        // provides access to differ, which must be constructed after callback
+        val differAccessor = arrayOf<AsyncPagedListDiffer<*>?>(null)
+
+        val callback = object : ListUpdateCallback {
+            override fun onInserted(position: Int, count: Int) {
+                assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
+            }
+
+            override fun onRemoved(position: Int, count: Int) {
+                assertEquals(expectedCount[0], differAccessor[0]!!.itemCount)
+            }
+
+            override fun onMoved(fromPosition: Int, toPosition: Int) {
+                fail("not expected")
+            }
+
+            override fun onChanged(position: Int, count: Int, payload: Any) {
+                fail("not expected")
+            }
+        }
+
+        val differ = createDiffer(callback, STRING_DIFF_CALLBACK)
+        differAccessor[0] = differ
+
+        val config = PagedList.Config.Builder()
+                .setPageSize(20)
+                .build()
+
+        // in the fast-add case...
+        expectedCount[0] = 5
+        assertEquals(0, differ.itemCount)
+        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 5), 0))
+        assertEquals(5, differ.itemCount)
+
+        // in the slow, diff on BG thread case...
+        expectedCount[0] = 10
+        assertEquals(5, differ.itemCount)
+        differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 10), 0))
+        drain()
+        assertEquals(10, differ.itemCount)
+
+        // and in the fast-remove case
+        expectedCount[0] = 0
+        assertEquals(10, differ.itemCount)
+        differ.submitList(null)
+        assertEquals(0, differ.itemCount)
+    }
+
+    private fun drainExceptDiffThread() {
+        var executed: Boolean
+        do {
+            executed = mPageLoadingThread.executeAll()
+            executed = mMainThread.executeAll() or executed
+        } while (executed)
+    }
+
+    private fun drain() {
+        var executed: Boolean
+        do {
+            executed = mPageLoadingThread.executeAll()
+            executed = mDiffThread.executeAll() or executed
+            executed = mMainThread.executeAll() or executed
+        } while (executed)
+    }
+
+    companion object {
+        private val ALPHABET_LIST = List(26) { "" + 'a' + it }
+
+        private val STRING_DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
+            override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
+                return oldItem == newItem
+            }
+
+            override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
+                return oldItem == newItem
+            }
+        }
+
+        private val IGNORE_CALLBACK = object : ListUpdateCallback {
+            override fun onInserted(position: Int, count: Int) {}
+
+            override fun onRemoved(position: Int, count: Int) {}
+
+            override fun onMoved(fromPosition: Int, toPosition: Int) {}
+
+            override fun onChanged(position: Int, count: Int, payload: Any) {}
+        }
+    }
+}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt
new file mode 100644
index 0000000..5bea75b
--- /dev/null
+++ b/paging/runtime/src/androidTest/java/androidx/paging/PagedStorageDiffHelperTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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 androidx.paging
+
+import android.support.test.filters.SmallTest
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListUpdateCallback
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PagedStorageDiffHelperTest {
+
+    @Test
+    fun sameListNoUpdates() {
+        validateTwoListDiff(
+                PagedStorage(5, listOf("a", "b", "c"), 5),
+                PagedStorage(5, listOf("a", "b", "c"), 5)) {
+            verifyZeroInteractions(it)
+        }
+    }
+
+    @Test
+    fun sameListNoUpdatesPlaceholder() {
+        val storageNoPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
+
+        val storageWithPlaceholder = PagedStorage(0, listOf("a", "b", "c"), 10)
+        storageWithPlaceholder.allocatePlaceholders(3, 0, 3,
+                /* ignored */ mock(PagedStorage.Callback::class.java))
+
+        // even though one has placeholders, and null counts are different...
+        assertEquals(10, storageNoPlaceholder.trailingNullCount)
+        assertEquals(7, storageWithPlaceholder.trailingNullCount)
+
+        // ... should be no interactions, since content still same
+        validateTwoListDiff(
+                storageNoPlaceholder,
+                storageWithPlaceholder) {
+            verifyZeroInteractions(it)
+        }
+    }
+
+    @Test
+    fun appendFill() {
+        validateTwoListDiff(
+                PagedStorage(5, listOf("a", "b"), 5),
+                PagedStorage(5, listOf("a", "b", "c"), 4)) {
+            verify(it).onRemoved(11, 1)
+            verify(it).onInserted(7, 1)
+            // NOTE: ideally would be onChanged(7, 1, null)
+            verifyNoMoreInteractions(it)
+        }
+    }
+
+    @Test
+    fun prependFill() {
+        validateTwoListDiff(
+                PagedStorage(5, listOf("b", "c"), 5),
+                PagedStorage(4, listOf("a", "b", "c"), 5)) {
+            verify(it).onRemoved(0, 1)
+            verify(it).onInserted(4, 1)
+            //NOTE: ideally would be onChanged(4, 1, null);
+            verifyNoMoreInteractions(it)
+        }
+    }
+
+    @Test
+    fun change() {
+        validateTwoListDiff(
+                PagedStorage(5, listOf("a1", "b1", "c1"), 5),
+                PagedStorage(5, listOf("a2", "b1", "c2"), 5)) {
+            verify(it).onChanged(5, 1, null)
+            verify(it).onChanged(7, 1, null)
+            verifyNoMoreInteractions(it)
+        }
+    }
+
+    companion object {
+        private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
+            override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
+                // first char means same item
+                return oldItem[0] == newItem[0]
+            }
+
+            override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
+                return oldItem == newItem
+            }
+        }
+
+        private fun validateTwoListDiff(oldList: PagedStorage<String>,
+                                        newList: PagedStorage<String>,
+                                        validator: (callback: ListUpdateCallback) -> Unit) {
+            val diffResult = PagedStorageDiffHelper.computeDiff(oldList, newList, DIFF_CALLBACK)
+
+            val listUpdateCallback = mock(ListUpdateCallback::class.java)
+            PagedStorageDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult)
+
+            validator(listUpdateCallback)
+        }
+    }
+}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
new file mode 100644
index 0000000..fb09091
--- /dev/null
+++ b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
@@ -0,0 +1,59 @@
+/*
+ * 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 androidx.paging
+
+class StringPagedList constructor(leadingNulls: Int, trailingNulls: Int, vararg items: String)
+        : PagedList<String>(PagedStorage<String>(), TestExecutor(), TestExecutor(), null,
+                PagedList.Config.Builder().setPageSize(1).build()), PagedStorage.Callback {
+    init {
+        @Suppress("UNCHECKED_CAST")
+        val keyedStorage = mStorage as PagedStorage<String>
+        keyedStorage.init(leadingNulls,
+                items.toList(),
+                trailingNulls,
+                0,
+                this)
+    }
+
+    internal override fun isContiguous(): Boolean {
+        return true
+    }
+
+    override fun getLastKey(): Any? {
+        return null
+    }
+
+    override fun dispatchUpdatesSinceSnapshot(storageSnapshot: PagedList<String>,
+            callback: PagedList.Callback) {
+    }
+
+    override fun loadAroundInternal(index: Int) {}
+
+    override fun onInitialized(count: Int) {}
+
+    override fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int) {}
+
+    override fun onPageAppended(endPosition: Int, changed: Int, added: Int) {}
+
+    override fun onPagePlaceholderInserted(pageIndex: Int) {}
+
+    override fun onPageInserted(start: Int, count: Int) {}
+
+    override fun getDataSource(): DataSource<*, String> {
+        throw UnsupportedOperationException()
+    }
+}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/TestExecutor.kt b/paging/runtime/src/androidTest/java/androidx/paging/TestExecutor.kt
new file mode 100644
index 0000000..ac02020
--- /dev/null
+++ b/paging/runtime/src/androidTest/java/androidx/paging/TestExecutor.kt
@@ -0,0 +1,39 @@
+/*
+ * 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 androidx.paging
+
+import java.util.LinkedList
+import java.util.concurrent.Executor
+
+class TestExecutor : Executor {
+    private val mTasks = LinkedList<Runnable>()
+
+    override fun execute(command: Runnable) {
+        mTasks.add(command)
+    }
+
+    fun executeAll(): Boolean {
+        val consumed = !mTasks.isEmpty()
+
+        var task = mTasks.poll()
+        while (task != null) {
+            task.run()
+            task = mTasks.poll()
+        }
+        return consumed
+    }
+}
\ No newline at end of file
diff --git a/paging/runtime/src/main/AndroidManifest.xml b/paging/runtime/src/main/AndroidManifest.xml
index dfc4bdf..cf34bb4 100644
--- a/paging/runtime/src/main/AndroidManifest.xml
+++ b/paging/runtime/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.paging.runtime">
+          package="androidx.paging.runtime">
 </manifest>
diff --git a/paging/runtime/src/main/java/android/arch/paging/AsyncPagedListDiffer.java b/paging/runtime/src/main/java/android/arch/paging/AsyncPagedListDiffer.java
deleted file mode 100644
index f00989c..0000000
--- a/paging/runtime/src/main/java/android/arch/paging/AsyncPagedListDiffer.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.lifecycle.LiveData;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.recyclerview.extensions.AsyncDifferConfig;
-import android.support.v7.util.AdapterListUpdateCallback;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-import android.support.v7.widget.RecyclerView;
-
-import java.util.concurrent.Executor;
-
-/**
- * Helper object for mapping a {@link PagedList} into a
- * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}.
- * <p>
- * For simplicity, the {@link PagedListAdapter} wrapper class can often be used instead of the
- * differ directly. This diff class is exposed for complex cases, and where overriding an adapter
- * base class to support paging isn't convenient.
- * <p>
- * When consuming a {@link LiveData} of PagedList, you can observe updates and dispatch them
- * directly to {@link #submitList(PagedList)}. The AsyncPagedListDiffer then can present this
- * updating data set simply for an adapter. It listens to PagedList loading callbacks, and uses
- * DiffUtil on a background thread to compute updates as new PagedLists are received.
- * <p>
- * It provides a simple list-like API with {@link #getItem(int)} and {@link #getItemCount()} for an
- * adapter to acquire and present data objects.
- * <p>
- * A complete usage pattern with Room would look like this:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- *     public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
- * }
- *
- * class MyViewModel extends ViewModel {
- *     public final LiveData&lt;PagedList&lt;User>> usersList;
- *     public MyViewModel(UserDao userDao) {
- *         usersList = new LivePagedListBuilder&lt;>(
- *                 userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
- *     }
- * }
- *
- * class MyActivity extends AppCompatActivity {
- *     {@literal @}Override
- *     public void onCreate(Bundle savedState) {
- *         super.onCreate(savedState);
- *         MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
- *         RecyclerView recyclerView = findViewById(R.id.user_list);
- *         final UserAdapter adapter = new UserAdapter();
- *         viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
- *         recyclerView.setAdapter(adapter);
- *     }
- * }
- *
- * class UserAdapter extends RecyclerView.Adapter&lt;UserViewHolder> {
- *     private final AsyncPagedListDiffer&lt;User> mDiffer
- *             = new AsyncPagedListDiffer(this, DIFF_CALLBACK);
- *     {@literal @}Override
- *     public int getItemCount() {
- *         return mDiffer.getItemCount();
- *     }
- *     public void submitList(PagedList&lt;User> pagedList) {
- *         mDiffer.submitList(pagedList);
- *     }
- *     {@literal @}Override
- *     public void onBindViewHolder(UserViewHolder holder, int position) {
- *         User user = mDiffer.getItem(position);
- *         if (user != null) {
- *             holder.bindTo(user);
- *         } else {
- *             // Null defines a placeholder item - AsyncPagedListDiffer will automatically
- *             // invalidate this row when the actual object is loaded from the database
- *             holder.clear();
- *         }
- *     }
- *     public static final DiffUtil.ItemCallback&lt;User> DIFF_CALLBACK =
- *             new DiffUtil.ItemCallback&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>
- *
- * @param <T> Type of the PagedLists this differ will receive.
- */
-public class AsyncPagedListDiffer<T> {
-    // updateCallback notifications must only be notified *after* new data and item count are stored
-    // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data
-    private final ListUpdateCallback mUpdateCallback;
-    private final AsyncDifferConfig<T> mConfig;
-
-    @SuppressWarnings("RestrictedApi")
-    Executor mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
-
-    // TODO: REAL API
-    interface PagedListListener<T> {
-        void onCurrentListChanged(@Nullable PagedList<T> currentList);
-    }
-
-    @Nullable
-    PagedListListener<T> mListener;
-
-    private boolean mIsContiguous;
-
-    private PagedList<T> mPagedList;
-    private PagedList<T> mSnapshot;
-
-    // Max generation of currently scheduled runnable
-    private int mMaxScheduledGeneration;
-
-    /**
-     * Convenience for {@code AsyncPagedListDiffer(new AdapterListUpdateCallback(adapter),
-     * new AsyncDifferConfig.Builder<T>(diffCallback).build();}
-     *
-     * @param adapter Adapter that will receive update signals.
-     * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
-     * compare items in the list.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
-            @NonNull DiffUtil.ItemCallback<T> diffCallback) {
-        mUpdateCallback = new AdapterListUpdateCallback(adapter);
-        mConfig = new AsyncDifferConfig.Builder<T>(diffCallback).build();
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback,
-            @NonNull AsyncDifferConfig<T> config) {
-        mUpdateCallback = listUpdateCallback;
-        mConfig = config;
-    }
-
-    private PagedList.Callback mPagedListCallback = new PagedList.Callback() {
-        @Override
-        public void onInserted(int position, int count) {
-            mUpdateCallback.onInserted(position, count);
-        }
-
-        @Override
-        public void onRemoved(int position, int count) {
-            mUpdateCallback.onRemoved(position, count);
-        }
-
-        @Override
-        public void onChanged(int position, int count) {
-            // NOTE: pass a null payload to convey null -> item
-            mUpdateCallback.onChanged(position, count, null);
-        }
-    };
-
-    /**
-     * Get the item from the current PagedList at the specified index.
-     * <p>
-     * Note that this operates on both loaded items and null padding within the PagedList.
-     *
-     * @param index Index of item to get, must be >= 0, and &lt; {@link #getItemCount()}.
-     * @return The item, or null, if a null placeholder is at the specified position.
-     */
-    @SuppressWarnings("WeakerAccess")
-    @Nullable
-    public T getItem(int index) {
-        if (mPagedList == null) {
-            if (mSnapshot == null) {
-                throw new IndexOutOfBoundsException(
-                        "Item count is zero, getItem() call is invalid");
-            } else {
-                return mSnapshot.get(index);
-            }
-        }
-
-        mPagedList.loadAround(index);
-        return mPagedList.get(index);
-    }
-
-    /**
-     * Get the number of items currently presented by this Differ. This value can be directly
-     * returned to {@link RecyclerView.Adapter#getItemCount()}.
-     *
-     * @return Number of items being presented.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public int getItemCount() {
-        if (mPagedList != null) {
-            return mPagedList.size();
-        }
-
-        return mSnapshot == null ? 0 : mSnapshot.size();
-    }
-
-    /**
-     * Pass a new PagedList to the differ.
-     * <p>
-     * If a PagedList is already present, a diff will be computed asynchronously on a background
-     * thread. When the diff is computed, it will be applied (dispatched to the
-     * {@link ListUpdateCallback}), and the new PagedList will be swapped in as the
-     * {@link #getCurrentList() current list}.
-     *
-     * @param pagedList The new PagedList.
-     */
-    public void submitList(final PagedList<T> pagedList) {
-        if (pagedList != null) {
-            if (mPagedList == null && mSnapshot == null) {
-                mIsContiguous = pagedList.isContiguous();
-            } else {
-                if (pagedList.isContiguous() != mIsContiguous) {
-                    throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both"
-                            + " contiguous and non-contiguous lists.");
-                }
-            }
-        }
-
-        if (pagedList == mPagedList) {
-            // nothing to do
-            return;
-        }
-
-        // incrementing generation means any currently-running diffs are discarded when they finish
-        final int runGeneration = ++mMaxScheduledGeneration;
-
-        if (pagedList == null) {
-            int removedCount = getItemCount();
-            if (mPagedList != null) {
-                mPagedList.removeWeakCallback(mPagedListCallback);
-                mPagedList = null;
-            } else if (mSnapshot != null) {
-                mSnapshot = null;
-            }
-            // dispatch update callback after updating mPagedList/mSnapshot
-            mUpdateCallback.onRemoved(0, removedCount);
-            if (mListener != null) {
-                mListener.onCurrentListChanged(null);
-            }
-            return;
-        }
-
-        if (mPagedList == null && mSnapshot == null) {
-            // fast simple first insert
-            mPagedList = pagedList;
-            pagedList.addWeakCallback(null, mPagedListCallback);
-
-            // dispatch update callback after updating mPagedList/mSnapshot
-            mUpdateCallback.onInserted(0, pagedList.size());
-
-            if (mListener != null) {
-                mListener.onCurrentListChanged(pagedList);
-            }
-            return;
-        }
-
-        if (mPagedList != null) {
-            // first update scheduled on this list, so capture mPages as a snapshot, removing
-            // callbacks so we don't have resolve updates against a moving target
-            mPagedList.removeWeakCallback(mPagedListCallback);
-            mSnapshot = (PagedList<T>) mPagedList.snapshot();
-            mPagedList = null;
-        }
-
-        if (mSnapshot == null || mPagedList != null) {
-            throw new IllegalStateException("must be in snapshot state to diff");
-        }
-
-        final PagedList<T> oldSnapshot = mSnapshot;
-        final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
-        mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
-            @Override
-            public void run() {
-                final DiffUtil.DiffResult result;
-                result = PagedStorageDiffHelper.computeDiff(
-                        oldSnapshot.mStorage,
-                        newSnapshot.mStorage,
-                        mConfig.getDiffCallback());
-
-                mMainThreadExecutor.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mMaxScheduledGeneration == runGeneration) {
-                            latchPagedList(pagedList, newSnapshot, result);
-                        }
-                    }
-                });
-            }
-        });
-    }
-
-    private void latchPagedList(
-            PagedList<T> newList, PagedList<T> diffSnapshot,
-            DiffUtil.DiffResult diffResult) {
-        if (mSnapshot == null || mPagedList != null) {
-            throw new IllegalStateException("must be in snapshot state to apply diff");
-        }
-
-        PagedList<T> previousSnapshot = mSnapshot;
-        mPagedList = newList;
-        mSnapshot = null;
-
-        // dispatch update callback after updating mPagedList/mSnapshot
-        PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
-                previousSnapshot.mStorage, newList.mStorage, diffResult);
-
-        newList.addWeakCallback(diffSnapshot, mPagedListCallback);
-        if (mListener != null) {
-            mListener.onCurrentListChanged(mPagedList);
-        }
-    }
-
-    /**
-     * Returns the PagedList currently being displayed by the differ.
-     * <p>
-     * This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
-     * because a diff is computed asynchronously between the new list and the current list before
-     * updating the currentList value. May be null if no PagedList is being presented.
-     *
-     * @return The list currently being displayed, may be null.
-     */
-    @SuppressWarnings("WeakerAccess")
-    @Nullable
-    public PagedList<T> getCurrentList() {
-        if (mSnapshot != null) {
-            return mSnapshot;
-        }
-        return mPagedList;
-    }
-}
diff --git a/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java b/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
deleted file mode 100644
index 2cc2026..0000000
--- a/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.lifecycle.ComputableLiveData;
-import android.arch.lifecycle.LiveData;
-import android.support.annotation.AnyThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.concurrent.Executor;
-
-/**
- * Builder for {@code LiveData<PagedList>}, given a {@link DataSource.Factory} and a
- * {@link PagedList.Config}.
- * <p>
- * The required parameters are in the constructor, so you can simply construct and build, or
- * optionally enable extra features (such as initial load key, or BoundaryCallback.
- *
- * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
- *             you're using PositionalDataSource.
- * @param <Value> Item type being presented.
- */
-public final class LivePagedListBuilder<Key, Value> {
-    private Key mInitialLoadKey;
-    private PagedList.Config mConfig;
-    private DataSource.Factory<Key, Value> mDataSourceFactory;
-    private PagedList.BoundaryCallback mBoundaryCallback;
-    private Executor mBackgroundThreadExecutor;
-
-    /**
-     * Creates a LivePagedListBuilder with required parameters.
-     *
-     * @param dataSourceFactory DataSource factory providing DataSource generations.
-     * @param config Paging configuration.
-     */
-    public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
-            @NonNull PagedList.Config config) {
-        mDataSourceFactory = dataSourceFactory;
-        mConfig = config;
-    }
-
-    /**
-     * Creates a LivePagedListBuilder with required parameters.
-     * <p>
-     * This method is a convenience for:
-     * <pre>
-     * LivePagedListBuilder(dataSourceFactory,
-     *         new PagedList.Config.Builder().setPageSize(pageSize).build())
-     * </pre>
-     *
-     * @param dataSourceFactory DataSource.Factory providing DataSource generations.
-     * @param pageSize Size of pages to load.
-     */
-    public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
-            int pageSize) {
-        this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build());
-    }
-
-    /**
-     * First loading key passed to the first PagedList/DataSource.
-     * <p>
-     * When a new PagedList/DataSource pair is created after the first, it acquires a load key from
-     * the previous generation so that data is loaded around the position already being observed.
-     *
-     * @param key Initial load key passed to the first PagedList/DataSource.
-     * @return this
-     */
-    @NonNull
-    public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
-        mInitialLoadKey = key;
-        return this;
-    }
-
-    /**
-     * Sets a {@link PagedList.BoundaryCallback} on each PagedList created, typically used to load
-     * additional data from network when paging from local storage.
-     * <p>
-     * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
-     * method is not called, or {@code null} is passed, you will not be notified when each
-     * DataSource runs out of data to provide to its PagedList.
-     * <p>
-     * If you are paging from a DataSource.Factory backed by local storage, you can set a
-     * BoundaryCallback to know when there is no more information to page from local storage.
-     * This is useful to page from the network when local storage is a cache of network data.
-     * <p>
-     * Note that when using a BoundaryCallback with a {@code LiveData<PagedList>}, method calls
-     * on the callback may be dispatched multiple times - one for each PagedList/DataSource
-     * pair. If loading network data from a BoundaryCallback, you should prevent multiple
-     * dispatches of the same method from triggering multiple simultaneous network loads.
-     *
-     * @param boundaryCallback The boundary callback for listening to PagedList load state.
-     * @return this
-     */
-    @SuppressWarnings("unused")
-    @NonNull
-    public LivePagedListBuilder<Key, Value> setBoundaryCallback(
-            @Nullable PagedList.BoundaryCallback<Value> boundaryCallback) {
-        mBoundaryCallback = boundaryCallback;
-        return this;
-    }
-
-    /**
-     * Sets executor which will be used for background loading of pages.
-     * <p>
-     * If not set, defaults to the Arch components I/O thread.
-     * <p>
-     * Does not affect initial load, which will be always be done on done on the Arch components
-     * I/O thread.
-     *
-     * @param backgroundThreadExecutor Executor for background DataSource loading.
-     * @return this
-     */
-    @SuppressWarnings("unused")
-    @NonNull
-    public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor(
-            @NonNull Executor backgroundThreadExecutor) {
-        mBackgroundThreadExecutor = backgroundThreadExecutor;
-        return this;
-    }
-
-    /**
-     * Constructs the {@code LiveData<PagedList>}.
-     * <p>
-     * No work (such as loading) is done immediately, the creation of the first PagedList is is
-     * deferred until the LiveData is observed.
-     *
-     * @return The LiveData of PagedLists
-     */
-    @NonNull
-    public LiveData<PagedList<Value>> build() {
-        if (mConfig == null) {
-            throw new IllegalArgumentException("PagedList.Config must be provided");
-        }
-        if (mDataSourceFactory == null) {
-            throw new IllegalArgumentException("DataSource.Factory must be provided");
-        }
-        if (mBackgroundThreadExecutor == null) {
-            mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
-        }
-
-        return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
-                ArchTaskExecutor.getMainThreadExecutor(), mBackgroundThreadExecutor);
-    }
-
-    @AnyThread
-    @NonNull
-    private static <Key, Value> LiveData<PagedList<Value>> create(
-            @Nullable final Key initialLoadKey,
-            @NonNull final PagedList.Config config,
-            @Nullable final PagedList.BoundaryCallback boundaryCallback,
-            @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
-            @NonNull final Executor mainThreadExecutor,
-            @NonNull final Executor backgroundThreadExecutor) {
-        return new ComputableLiveData<PagedList<Value>>() {
-            @Nullable
-            private PagedList<Value> mList;
-            @Nullable
-            private DataSource<Key, Value> mDataSource;
-
-            private final DataSource.InvalidatedCallback mCallback =
-                    new DataSource.InvalidatedCallback() {
-                        @Override
-                        public void onInvalidated() {
-                            invalidate();
-                        }
-                    };
-
-            @Override
-            protected PagedList<Value> compute() {
-                @Nullable Key initializeKey = initialLoadKey;
-                if (mList != null) {
-                    //noinspection unchecked
-                    initializeKey = (Key) mList.getLastKey();
-                }
-
-                do {
-                    if (mDataSource != null) {
-                        mDataSource.removeInvalidatedCallback(mCallback);
-                    }
-
-                    mDataSource = dataSourceFactory.create();
-                    mDataSource.addInvalidatedCallback(mCallback);
-
-                    mList = new PagedList.Builder<>(mDataSource, config)
-                            .setMainThreadExecutor(mainThreadExecutor)
-                            .setBackgroundThreadExecutor(backgroundThreadExecutor)
-                            .setBoundaryCallback(boundaryCallback)
-                            .setInitialKey(initializeKey)
-                            .build();
-                } while (mList.isDetached());
-                return mList;
-            }
-        }.getLiveData();
-    }
-}
diff --git a/paging/runtime/src/main/java/android/arch/paging/LivePagedListProvider.java b/paging/runtime/src/main/java/android/arch/paging/LivePagedListProvider.java
deleted file mode 100644
index de470fc..0000000
--- a/paging/runtime/src/main/java/android/arch/paging/LivePagedListProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-
-// NOTE: Room 1.0 depends on this class, so it should not be removed until
-// we can require a version of Room that uses DataSource.Factory directly
-
-/**
- * @hide
- */
-@Deprecated
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class LivePagedListProvider<Key, Value> extends DataSource.Factory<Key, Value> {
-
-    @Override
-    public DataSource<Key, Value> create() {
-        return createDataSource();
-    }
-
-    @WorkerThread
-    protected abstract DataSource<Key, Value> createDataSource();
-}
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java b/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
deleted file mode 100644
index 4728828..0000000
--- a/paging/runtime/src/main/java/android/arch/paging/PagedListAdapter.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.recyclerview.extensions.AsyncDifferConfig;
-import android.support.v7.util.AdapterListUpdateCallback;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.widget.RecyclerView;
-
-/**
- * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from
- * {@link PagedList}s in a {@link RecyclerView}.
- * <p>
- * This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common
- * default behavior for item counting, and listening to PagedList update callbacks.
- * <p>
- * While using a LiveData&lt;PagedList> is an easy way to provide data to the adapter, it isn't
- * required - you can use {@link #submitList(PagedList)} when new lists are available.
- * <p>
- * PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on
- * a background thread to compute fine grained updates as new PagedLists are received.
- * <p>
- * Handles both the internal paging of the list as more data is loaded, and updates in the form of
- * new PagedLists.
- * <p>
- * A complete usage pattern with Room would look like this:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- *     public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
- * }
- *
- * class MyViewModel extends ViewModel {
- *     public final LiveData&lt;PagedList&lt;User>> usersList;
- *     public MyViewModel(UserDao userDao) {
- *         usersList = new LivePagedListBuilder&lt;>(
- *                 userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
- *     }
- * }
- *
- * class MyActivity extends AppCompatActivity {
- *     {@literal @}Override
- *     public void onCreate(Bundle savedState) {
- *         super.onCreate(savedState);
- *         MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
- *         RecyclerView recyclerView = findViewById(R.id.user_list);
- *         UserAdapter&lt;User> adapter = new UserAdapter();
- *         viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
- *         recyclerView.setAdapter(adapter);
- *     }
- * }
- *
- * class UserAdapter extends PagedListAdapter&lt;User, UserViewHolder> {
- *     public UserAdapter() {
- *         super(DIFF_CALLBACK);
- *     }
- *     {@literal @}Override
- *     public void onBindViewHolder(UserViewHolder holder, int position) {
- *         User user = getItem(position);
- *         if (user != null) {
- *             holder.bindTo(user);
- *         } else {
- *             // Null defines a placeholder item - PagedListAdapter will automatically invalidate
- *             // this row when the actual object is loaded from the database
- *             holder.clear();
- *         }
- *     }
- *     public static final DiffUtil.ItemCallback&lt;User> DIFF_CALLBACK =
- *             new DiffUtil.ItemCallback&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
- * class should refer to {@link AsyncPagedListDiffer}, which provides the mapping from paging
- * events to adapter-friendly callbacks.
- *
- * @param <T> Type of the PagedLists this Adapter will receive.
- * @param <VH> A class that extends ViewHolder that will be used by the adapter.
- */
-public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
-        extends RecyclerView.Adapter<VH> {
-    private final AsyncPagedListDiffer<T> mDiffer;
-    private final AsyncPagedListDiffer.PagedListListener<T> mListener =
-            new AsyncPagedListDiffer.PagedListListener<T>() {
-        @Override
-        public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
-            PagedListAdapter.this.onCurrentListChanged(currentList);
-        }
-    };
-
-    /**
-     * Creates a PagedListAdapter with default threading and
-     * {@link android.support.v7.util.ListUpdateCallback}.
-     *
-     * Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading
-     * behavior.
-     *
-     * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
-     *                     compare items in the list.
-     */
-    protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
-        mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
-        mDiffer.mListener = mListener;
-    }
-
-    @SuppressWarnings("unused, WeakerAccess")
-    protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) {
-        mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config);
-        mDiffer.mListener = mListener;
-    }
-
-    /**
-     * Set the new list to be displayed.
-     * <p>
-     * If a list is already being displayed, a diff will be computed on a background thread, which
-     * will dispatch Adapter.notifyItem events on the main thread.
-     *
-     * @param pagedList The new list to be displayed.
-     */
-    public void submitList(PagedList<T> pagedList) {
-        mDiffer.submitList(pagedList);
-    }
-
-    @Nullable
-    protected T getItem(int position) {
-        return mDiffer.getItem(position);
-    }
-
-    @Override
-    public int getItemCount() {
-        return mDiffer.getItemCount();
-    }
-
-    /**
-     * Returns the PagedList currently being displayed by the Adapter.
-     * <p>
-     * This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
-     * because a diff is computed asynchronously between the new list and the current list before
-     * updating the currentList value. May be null if no PagedList is being presented.
-     *
-     * @return The list currently being displayed.
-     */
-    @Nullable
-    public PagedList<T> getCurrentList() {
-        return mDiffer.getCurrentList();
-    }
-
-    /**
-     * Called when the current PagedList is updated.
-     * <p>
-     * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't
-     * needed (such as when the first list is passed, or the list is cleared). In either case,
-     * PagedListAdapter will simply call
-     * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}.
-     * <p>
-     * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList
-     * to a snapshot version of the PagedList during a diff. This means you cannot observe each
-     * PagedList via this method.
-     *
-     * @param currentList new PagedList being displayed, may be null.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
-    }
-}
diff --git a/paging/runtime/src/main/java/android/arch/paging/PagedStorageDiffHelper.java b/paging/runtime/src/main/java/android/arch/paging/PagedStorageDiffHelper.java
deleted file mode 100644
index f495608..0000000
--- a/paging/runtime/src/main/java/android/arch/paging/PagedStorageDiffHelper.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.Nullable;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-
-class PagedStorageDiffHelper {
-    private PagedStorageDiffHelper() {
-    }
-
-    static <T> DiffUtil.DiffResult computeDiff(
-            final PagedStorage<T> oldList,
-            final PagedStorage<T> newList,
-            final DiffUtil.ItemCallback<T> diffCallback) {
-        final int oldOffset = oldList.computeLeadingNulls();
-        final int newOffset = newList.computeLeadingNulls();
-
-        final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls();
-        final int newSize = newList.size() - newOffset - newList.computeTrailingNulls();
-
-        return DiffUtil.calculateDiff(new DiffUtil.Callback() {
-            @Nullable
-            @Override
-            public Object getChangePayload(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.get(oldItemPosition + oldOffset);
-                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
-                if (oldItem == null || newItem == null) {
-                    return null;
-                }
-                return diffCallback.getChangePayload(oldItem, newItem);
-            }
-
-            @Override
-            public int getOldListSize() {
-                return oldSize;
-            }
-
-            @Override
-            public int getNewListSize() {
-                return newSize;
-            }
-
-            @Override
-            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.get(oldItemPosition + oldOffset);
-                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
-                if (oldItem == newItem) {
-                    return true;
-                }
-                //noinspection SimplifiableIfStatement
-                if (oldItem == null || newItem == null) {
-                    return false;
-                }
-                return diffCallback.areItemsTheSame(oldItem, newItem);
-            }
-
-            @Override
-            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
-                T oldItem = oldList.get(oldItemPosition + oldOffset);
-                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
-                if (oldItem == newItem) {
-                    return true;
-                }
-                //noinspection SimplifiableIfStatement
-                if (oldItem == null || newItem == null) {
-                    return false;
-                }
-
-                return diffCallback.areContentsTheSame(oldItem, newItem);
-            }
-        }, true);
-    }
-
-    private static class OffsettingListUpdateCallback implements ListUpdateCallback {
-        private final int mOffset;
-        private final ListUpdateCallback mCallback;
-
-        private OffsettingListUpdateCallback(int offset, ListUpdateCallback callback) {
-            mOffset = offset;
-            mCallback = callback;
-        }
-
-        @Override
-        public void onInserted(int position, int count) {
-            mCallback.onInserted(position + mOffset, count);
-        }
-
-        @Override
-        public void onRemoved(int position, int count) {
-            mCallback.onRemoved(position + mOffset, count);
-        }
-
-        @Override
-        public void onMoved(int fromPosition, int toPosition) {
-            mCallback.onRemoved(fromPosition + mOffset, toPosition + mOffset);
-        }
-
-        @Override
-        public void onChanged(int position, int count, Object payload) {
-            mCallback.onChanged(position + mOffset, count, payload);
-        }
-    }
-
-    /**
-     * TODO: improve diffing logic
-     *
-     * This function currently does a naive diff, assuming null does not become an item, and vice
-     * versa (so it won't dispatch onChange events for these). It's similar to passing a list with
-     * leading/trailing nulls in the beginning / end to DiffUtil, but dispatches the remove/insert
-     * for changed nulls at the beginning / end of the list.
-     *
-     * Note: if lists mutate between diffing the snapshot and dispatching the diff here, then we
-     * handle this by passing the snapshot to the callback, and dispatching those changes
-     * immediately after dispatching this diff.
-     */
-    static <T> void dispatchDiff(ListUpdateCallback callback,
-            final PagedStorage<T> oldList,
-            final PagedStorage<T> newList,
-            final DiffUtil.DiffResult diffResult) {
-
-        final int trailingOld = oldList.computeTrailingNulls();
-        final int trailingNew = newList.computeTrailingNulls();
-        final int leadingOld = oldList.computeLeadingNulls();
-        final int leadingNew = newList.computeLeadingNulls();
-
-        if (trailingOld == 0
-                && trailingNew == 0
-                && leadingOld == 0
-                && leadingNew == 0) {
-            // Simple case, dispatch & return
-            diffResult.dispatchUpdatesTo(callback);
-            return;
-        }
-
-        // First, remove or insert trailing nulls
-        if (trailingOld > trailingNew) {
-            int count = trailingOld - trailingNew;
-            callback.onRemoved(oldList.size() - count, count);
-        } else if (trailingOld < trailingNew) {
-            callback.onInserted(oldList.size(), trailingNew - trailingOld);
-        }
-
-        // Second, remove or insert leading nulls
-        if (leadingOld > leadingNew) {
-            callback.onRemoved(0, leadingOld - leadingNew);
-        } else if (leadingOld < leadingNew) {
-            callback.onInserted(0, leadingNew - leadingOld);
-        }
-
-        // apply the diff, with an offset if needed
-        if (leadingNew != 0) {
-            diffResult.dispatchUpdatesTo(new OffsettingListUpdateCallback(leadingNew, callback));
-        } else {
-            diffResult.dispatchUpdatesTo(callback);
-        }
-    }
-}
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
new file mode 100644
index 0000000..f30163d
--- /dev/null
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
@@ -0,0 +1,353 @@
+/*
+ * 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.lifecycle.LiveData;
+import androidx.recyclerview.widget.AdapterListUpdateCallback;
+import androidx.recyclerview.widget.AsyncDifferConfig;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.ListUpdateCallback;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Helper object for mapping a {@link PagedList} into a
+ * {@link androidx.recyclerview.widget.RecyclerView.Adapter RecyclerView.Adapter}.
+ * <p>
+ * For simplicity, the {@link PagedListAdapter} wrapper class can often be used instead of the
+ * differ directly. This diff class is exposed for complex cases, and where overriding an adapter
+ * base class to support paging isn't convenient.
+ * <p>
+ * When consuming a {@link LiveData} of PagedList, you can observe updates and dispatch them
+ * directly to {@link #submitList(PagedList)}. The AsyncPagedListDiffer then can present this
+ * updating data set simply for an adapter. It listens to PagedList loading callbacks, and uses
+ * DiffUtil on a background thread to compute updates as new PagedLists are received.
+ * <p>
+ * It provides a simple list-like API with {@link #getItem(int)} and {@link #getItemCount()} for an
+ * adapter to acquire and present data objects.
+ * <p>
+ * A complete usage pattern with Room would look like this:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ *     public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
+ * }
+ *
+ * class MyViewModel extends ViewModel {
+ *     public final LiveData&lt;PagedList&lt;User>> usersList;
+ *     public MyViewModel(UserDao userDao) {
+ *         usersList = new LivePagedListBuilder&lt;>(
+ *                 userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
+ *     }
+ * }
+ *
+ * class MyActivity extends AppCompatActivity {
+ *     {@literal @}Override
+ *     public void onCreate(Bundle savedState) {
+ *         super.onCreate(savedState);
+ *         MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
+ *         RecyclerView recyclerView = findViewById(R.id.user_list);
+ *         final UserAdapter adapter = new UserAdapter();
+ *         viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
+ *         recyclerView.setAdapter(adapter);
+ *     }
+ * }
+ *
+ * class UserAdapter extends RecyclerView.Adapter&lt;UserViewHolder> {
+ *     private final AsyncPagedListDiffer&lt;User> mDiffer
+ *             = new AsyncPagedListDiffer(this, DIFF_CALLBACK);
+ *     {@literal @}Override
+ *     public int getItemCount() {
+ *         return mDiffer.getItemCount();
+ *     }
+ *     public void submitList(PagedList&lt;User> pagedList) {
+ *         mDiffer.submitList(pagedList);
+ *     }
+ *     {@literal @}Override
+ *     public void onBindViewHolder(UserViewHolder holder, int position) {
+ *         User user = mDiffer.getItem(position);
+ *         if (user != null) {
+ *             holder.bindTo(user);
+ *         } else {
+ *             // Null defines a placeholder item - AsyncPagedListDiffer will automatically
+ *             // invalidate this row when the actual object is loaded from the database
+ *             holder.clear();
+ *         }
+ *     }
+ *     public static final DiffUtil.ItemCallback&lt;User> DIFF_CALLBACK =
+ *             new DiffUtil.ItemCallback&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>
+ *
+ * @param <T> Type of the PagedLists this differ will receive.
+ */
+public class AsyncPagedListDiffer<T> {
+    // updateCallback notifications must only be notified *after* new data and item count are stored
+    // this ensures Adapter#notifyItemRangeInserted etc are accessing the new data
+    private final ListUpdateCallback mUpdateCallback;
+    private final AsyncDifferConfig<T> mConfig;
+
+    @SuppressWarnings("RestrictedApi")
+    Executor mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
+
+    // TODO: REAL API
+    interface PagedListListener<T> {
+        void onCurrentListChanged(@Nullable PagedList<T> currentList);
+    }
+
+    @Nullable
+    PagedListListener<T> mListener;
+
+    private boolean mIsContiguous;
+
+    private PagedList<T> mPagedList;
+    private PagedList<T> mSnapshot;
+
+    // Max generation of currently scheduled runnable
+    private int mMaxScheduledGeneration;
+
+    /**
+     * Convenience for {@code AsyncPagedListDiffer(new AdapterListUpdateCallback(adapter),
+     * new AsyncDifferConfig.Builder<T>(diffCallback).build();}
+     *
+     * @param adapter Adapter that will receive update signals.
+     * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
+     * compare items in the list.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
+            @NonNull DiffUtil.ItemCallback<T> diffCallback) {
+        mUpdateCallback = new AdapterListUpdateCallback(adapter);
+        mConfig = new AsyncDifferConfig.Builder<T>(diffCallback).build();
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback,
+            @NonNull AsyncDifferConfig<T> config) {
+        mUpdateCallback = listUpdateCallback;
+        mConfig = config;
+    }
+
+    private PagedList.Callback mPagedListCallback = new PagedList.Callback() {
+        @Override
+        public void onInserted(int position, int count) {
+            mUpdateCallback.onInserted(position, count);
+        }
+
+        @Override
+        public void onRemoved(int position, int count) {
+            mUpdateCallback.onRemoved(position, count);
+        }
+
+        @Override
+        public void onChanged(int position, int count) {
+            // NOTE: pass a null payload to convey null -> item
+            mUpdateCallback.onChanged(position, count, null);
+        }
+    };
+
+    /**
+     * Get the item from the current PagedList at the specified index.
+     * <p>
+     * Note that this operates on both loaded items and null padding within the PagedList.
+     *
+     * @param index Index of item to get, must be >= 0, and &lt; {@link #getItemCount()}.
+     * @return The item, or null, if a null placeholder is at the specified position.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @Nullable
+    public T getItem(int index) {
+        if (mPagedList == null) {
+            if (mSnapshot == null) {
+                throw new IndexOutOfBoundsException(
+                        "Item count is zero, getItem() call is invalid");
+            } else {
+                return mSnapshot.get(index);
+            }
+        }
+
+        mPagedList.loadAround(index);
+        return mPagedList.get(index);
+    }
+
+    /**
+     * Get the number of items currently presented by this Differ. This value can be directly
+     * returned to {@link RecyclerView.Adapter#getItemCount()}.
+     *
+     * @return Number of items being presented.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public int getItemCount() {
+        if (mPagedList != null) {
+            return mPagedList.size();
+        }
+
+        return mSnapshot == null ? 0 : mSnapshot.size();
+    }
+
+    /**
+     * Pass a new PagedList to the differ.
+     * <p>
+     * If a PagedList is already present, a diff will be computed asynchronously on a background
+     * thread. When the diff is computed, it will be applied (dispatched to the
+     * {@link ListUpdateCallback}), and the new PagedList will be swapped in as the
+     * {@link #getCurrentList() current list}.
+     *
+     * @param pagedList The new PagedList.
+     */
+    public void submitList(final PagedList<T> pagedList) {
+        if (pagedList != null) {
+            if (mPagedList == null && mSnapshot == null) {
+                mIsContiguous = pagedList.isContiguous();
+            } else {
+                if (pagedList.isContiguous() != mIsContiguous) {
+                    throw new IllegalArgumentException("AsyncPagedListDiffer cannot handle both"
+                            + " contiguous and non-contiguous lists.");
+                }
+            }
+        }
+
+        if (pagedList == mPagedList) {
+            // nothing to do
+            return;
+        }
+
+        // incrementing generation means any currently-running diffs are discarded when they finish
+        final int runGeneration = ++mMaxScheduledGeneration;
+
+        if (pagedList == null) {
+            int removedCount = getItemCount();
+            if (mPagedList != null) {
+                mPagedList.removeWeakCallback(mPagedListCallback);
+                mPagedList = null;
+            } else if (mSnapshot != null) {
+                mSnapshot = null;
+            }
+            // dispatch update callback after updating mPagedList/mSnapshot
+            mUpdateCallback.onRemoved(0, removedCount);
+            if (mListener != null) {
+                mListener.onCurrentListChanged(null);
+            }
+            return;
+        }
+
+        if (mPagedList == null && mSnapshot == null) {
+            // fast simple first insert
+            mPagedList = pagedList;
+            pagedList.addWeakCallback(null, mPagedListCallback);
+
+            // dispatch update callback after updating mPagedList/mSnapshot
+            mUpdateCallback.onInserted(0, pagedList.size());
+
+            if (mListener != null) {
+                mListener.onCurrentListChanged(pagedList);
+            }
+            return;
+        }
+
+        if (mPagedList != null) {
+            // first update scheduled on this list, so capture mPages as a snapshot, removing
+            // callbacks so we don't have resolve updates against a moving target
+            mPagedList.removeWeakCallback(mPagedListCallback);
+            mSnapshot = (PagedList<T>) mPagedList.snapshot();
+            mPagedList = null;
+        }
+
+        if (mSnapshot == null || mPagedList != null) {
+            throw new IllegalStateException("must be in snapshot state to diff");
+        }
+
+        final PagedList<T> oldSnapshot = mSnapshot;
+        final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
+        mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
+            @Override
+            public void run() {
+                final DiffUtil.DiffResult result;
+                result = PagedStorageDiffHelper.computeDiff(
+                        oldSnapshot.mStorage,
+                        newSnapshot.mStorage,
+                        mConfig.getDiffCallback());
+
+                mMainThreadExecutor.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mMaxScheduledGeneration == runGeneration) {
+                            latchPagedList(pagedList, newSnapshot, result);
+                        }
+                    }
+                });
+            }
+        });
+    }
+
+    private void latchPagedList(
+            PagedList<T> newList, PagedList<T> diffSnapshot,
+            DiffUtil.DiffResult diffResult) {
+        if (mSnapshot == null || mPagedList != null) {
+            throw new IllegalStateException("must be in snapshot state to apply diff");
+        }
+
+        PagedList<T> previousSnapshot = mSnapshot;
+        mPagedList = newList;
+        mSnapshot = null;
+
+        // dispatch update callback after updating mPagedList/mSnapshot
+        PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
+                previousSnapshot.mStorage, newList.mStorage, diffResult);
+
+        newList.addWeakCallback(diffSnapshot, mPagedListCallback);
+        if (mListener != null) {
+            mListener.onCurrentListChanged(mPagedList);
+        }
+    }
+
+    /**
+     * Returns the PagedList currently being displayed by the differ.
+     * <p>
+     * This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
+     * because a diff is computed asynchronously between the new list and the current list before
+     * updating the currentList value. May be null if no PagedList is being presented.
+     *
+     * @return The list currently being displayed, may be null.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @Nullable
+    public PagedList<T> getCurrentList() {
+        if (mSnapshot != null) {
+            return mSnapshot;
+        }
+        return mPagedList;
+    }
+}
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
new file mode 100644
index 0000000..0cb0a25
--- /dev/null
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
@@ -0,0 +1,211 @@
+/*
+ * 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 androidx.paging;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.lifecycle.ComputableLiveData;
+import androidx.lifecycle.LiveData;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Builder for {@code LiveData<PagedList>}, given a {@link DataSource.Factory} and a
+ * {@link PagedList.Config}.
+ * <p>
+ * The required parameters are in the constructor, so you can simply construct and build, or
+ * optionally enable extra features (such as initial load key, or BoundaryCallback.
+ *
+ * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
+ *             you're using PositionalDataSource.
+ * @param <Value> Item type being presented.
+ */
+public final class LivePagedListBuilder<Key, Value> {
+    private Key mInitialLoadKey;
+    private PagedList.Config mConfig;
+    private DataSource.Factory<Key, Value> mDataSourceFactory;
+    private PagedList.BoundaryCallback mBoundaryCallback;
+    private Executor mBackgroundThreadExecutor;
+
+    /**
+     * Creates a LivePagedListBuilder with required parameters.
+     *
+     * @param dataSourceFactory DataSource factory providing DataSource generations.
+     * @param config Paging configuration.
+     */
+    public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+            @NonNull PagedList.Config config) {
+        mDataSourceFactory = dataSourceFactory;
+        mConfig = config;
+    }
+
+    /**
+     * Creates a LivePagedListBuilder with required parameters.
+     * <p>
+     * This method is a convenience for:
+     * <pre>
+     * LivePagedListBuilder(dataSourceFactory,
+     *         new PagedList.Config.Builder().setPageSize(pageSize).build())
+     * </pre>
+     *
+     * @param dataSourceFactory DataSource.Factory providing DataSource generations.
+     * @param pageSize Size of pages to load.
+     */
+    public LivePagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+            int pageSize) {
+        this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build());
+    }
+
+    /**
+     * First loading key passed to the first PagedList/DataSource.
+     * <p>
+     * When a new PagedList/DataSource pair is created after the first, it acquires a load key from
+     * the previous generation so that data is loaded around the position already being observed.
+     *
+     * @param key Initial load key passed to the first PagedList/DataSource.
+     * @return this
+     */
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
+        mInitialLoadKey = key;
+        return this;
+    }
+
+    /**
+     * Sets a {@link PagedList.BoundaryCallback} on each PagedList created, typically used to load
+     * additional data from network when paging from local storage.
+     * <p>
+     * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
+     * method is not called, or {@code null} is passed, you will not be notified when each
+     * DataSource runs out of data to provide to its PagedList.
+     * <p>
+     * If you are paging from a DataSource.Factory backed by local storage, you can set a
+     * BoundaryCallback to know when there is no more information to page from local storage.
+     * This is useful to page from the network when local storage is a cache of network data.
+     * <p>
+     * Note that when using a BoundaryCallback with a {@code LiveData<PagedList>}, method calls
+     * on the callback may be dispatched multiple times - one for each PagedList/DataSource
+     * pair. If loading network data from a BoundaryCallback, you should prevent multiple
+     * dispatches of the same method from triggering multiple simultaneous network loads.
+     *
+     * @param boundaryCallback The boundary callback for listening to PagedList load state.
+     * @return this
+     */
+    @SuppressWarnings("unused")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setBoundaryCallback(
+            @Nullable PagedList.BoundaryCallback<Value> boundaryCallback) {
+        mBoundaryCallback = boundaryCallback;
+        return this;
+    }
+
+    /**
+     * Sets executor which will be used for background loading of pages.
+     * <p>
+     * If not set, defaults to the Arch components I/O thread.
+     * <p>
+     * Does not affect initial load, which will be always be done on done on the Arch components
+     * I/O thread.
+     *
+     * @param backgroundThreadExecutor Executor for background DataSource loading.
+     * @return this
+     */
+    @SuppressWarnings("unused")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor(
+            @NonNull Executor backgroundThreadExecutor) {
+        mBackgroundThreadExecutor = backgroundThreadExecutor;
+        return this;
+    }
+
+    /**
+     * Constructs the {@code LiveData<PagedList>}.
+     * <p>
+     * No work (such as loading) is done immediately, the creation of the first PagedList is is
+     * deferred until the LiveData is observed.
+     *
+     * @return The LiveData of PagedLists
+     */
+    @NonNull
+    public LiveData<PagedList<Value>> build() {
+        if (mConfig == null) {
+            throw new IllegalArgumentException("PagedList.Config must be provided");
+        }
+        if (mDataSourceFactory == null) {
+            throw new IllegalArgumentException("DataSource.Factory must be provided");
+        }
+        if (mBackgroundThreadExecutor == null) {
+            mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
+        }
+
+        return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
+                ArchTaskExecutor.getMainThreadExecutor(), mBackgroundThreadExecutor);
+    }
+
+    @AnyThread
+    @NonNull
+    private static <Key, Value> LiveData<PagedList<Value>> create(
+            @Nullable final Key initialLoadKey,
+            @NonNull final PagedList.Config config,
+            @Nullable final PagedList.BoundaryCallback boundaryCallback,
+            @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
+            @NonNull final Executor mainThreadExecutor,
+            @NonNull final Executor backgroundThreadExecutor) {
+        return new ComputableLiveData<PagedList<Value>>() {
+            @Nullable
+            private PagedList<Value> mList;
+            @Nullable
+            private DataSource<Key, Value> mDataSource;
+
+            private final DataSource.InvalidatedCallback mCallback =
+                    new DataSource.InvalidatedCallback() {
+                        @Override
+                        public void onInvalidated() {
+                            invalidate();
+                        }
+                    };
+
+            @Override
+            protected PagedList<Value> compute() {
+                @Nullable Key initializeKey = initialLoadKey;
+                if (mList != null) {
+                    //noinspection unchecked
+                    initializeKey = (Key) mList.getLastKey();
+                }
+
+                do {
+                    if (mDataSource != null) {
+                        mDataSource.removeInvalidatedCallback(mCallback);
+                    }
+
+                    mDataSource = dataSourceFactory.create();
+                    mDataSource.addInvalidatedCallback(mCallback);
+
+                    mList = new PagedList.Builder<>(mDataSource, config)
+                            .setMainThreadExecutor(mainThreadExecutor)
+                            .setBackgroundThreadExecutor(backgroundThreadExecutor)
+                            .setBoundaryCallback(boundaryCallback)
+                            .setInitialKey(initializeKey)
+                            .build();
+                } while (mList.isDetached());
+                return mList;
+            }
+        }.getLiveData();
+    }
+}
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListProvider.java b/paging/runtime/src/main/java/androidx/paging/LivePagedListProvider.java
new file mode 100644
index 0000000..0fb958d
--- /dev/null
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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 androidx.paging;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
+
+// NOTE: Room 1.0 depends on this class, so it should not be removed until
+// we can require a version of Room that uses DataSource.Factory directly
+
+/**
+ * @hide
+ * @deprecated Do not use this class.
+ */
+@Deprecated
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class LivePagedListProvider<Key, Value> extends DataSource.Factory<Key, Value> {
+
+    @Override
+    public DataSource<Key, Value> create() {
+        return createDataSource();
+    }
+
+    @WorkerThread
+    protected abstract DataSource<Key, Value> createDataSource();
+}
diff --git a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
new file mode 100644
index 0000000..72148ad
--- /dev/null
+++ b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
@@ -0,0 +1,195 @@
+/*
+ * 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 androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.AdapterListUpdateCallback;
+import androidx.recyclerview.widget.AsyncDifferConfig;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from
+ * {@link PagedList}s in a {@link RecyclerView}.
+ * <p>
+ * This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common
+ * default behavior for item counting, and listening to PagedList update callbacks.
+ * <p>
+ * While using a LiveData&lt;PagedList> is an easy way to provide data to the adapter, it isn't
+ * required - you can use {@link #submitList(PagedList)} when new lists are available.
+ * <p>
+ * PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on
+ * a background thread to compute fine grained updates as new PagedLists are received.
+ * <p>
+ * Handles both the internal paging of the list as more data is loaded, and updates in the form of
+ * new PagedLists.
+ * <p>
+ * A complete usage pattern with Room would look like this:
+ * <pre>
+ * {@literal @}Dao
+ * interface UserDao {
+ *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ *     public abstract DataSource.Factory&lt;Integer, User> usersByLastName();
+ * }
+ *
+ * class MyViewModel extends ViewModel {
+ *     public final LiveData&lt;PagedList&lt;User>> usersList;
+ *     public MyViewModel(UserDao userDao) {
+ *         usersList = new LivePagedListBuilder&lt;>(
+ *                 userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
+ *     }
+ * }
+ *
+ * class MyActivity extends AppCompatActivity {
+ *     {@literal @}Override
+ *     public void onCreate(Bundle savedState) {
+ *         super.onCreate(savedState);
+ *         MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
+ *         RecyclerView recyclerView = findViewById(R.id.user_list);
+ *         UserAdapter&lt;User> adapter = new UserAdapter();
+ *         viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
+ *         recyclerView.setAdapter(adapter);
+ *     }
+ * }
+ *
+ * class UserAdapter extends PagedListAdapter&lt;User, UserViewHolder> {
+ *     public UserAdapter() {
+ *         super(DIFF_CALLBACK);
+ *     }
+ *     {@literal @}Override
+ *     public void onBindViewHolder(UserViewHolder holder, int position) {
+ *         User user = getItem(position);
+ *         if (user != null) {
+ *             holder.bindTo(user);
+ *         } else {
+ *             // Null defines a placeholder item - PagedListAdapter will automatically invalidate
+ *             // this row when the actual object is loaded from the database
+ *             holder.clear();
+ *         }
+ *     }
+ *     public static final DiffUtil.ItemCallback&lt;User> DIFF_CALLBACK =
+ *             new DiffUtil.ItemCallback&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
+ * class should refer to {@link AsyncPagedListDiffer}, which provides the mapping from paging
+ * events to adapter-friendly callbacks.
+ *
+ * @param <T> Type of the PagedLists this Adapter will receive.
+ * @param <VH> A class that extends ViewHolder that will be used by the adapter.
+ */
+public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
+        extends RecyclerView.Adapter<VH> {
+    private final AsyncPagedListDiffer<T> mDiffer;
+    private final AsyncPagedListDiffer.PagedListListener<T> mListener =
+            new AsyncPagedListDiffer.PagedListListener<T>() {
+        @Override
+        public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
+            PagedListAdapter.this.onCurrentListChanged(currentList);
+        }
+    };
+
+    /**
+     * Creates a PagedListAdapter with default threading and
+     * {@link androidx.recyclerview.widget.ListUpdateCallback}.
+     *
+     * Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading
+     * behavior.
+     *
+     * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
+     *                     compare items in the list.
+     */
+    protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
+        mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
+        mDiffer.mListener = mListener;
+    }
+
+    @SuppressWarnings("unused, WeakerAccess")
+    protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) {
+        mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config);
+        mDiffer.mListener = mListener;
+    }
+
+    /**
+     * Set the new list to be displayed.
+     * <p>
+     * If a list is already being displayed, a diff will be computed on a background thread, which
+     * will dispatch Adapter.notifyItem events on the main thread.
+     *
+     * @param pagedList The new list to be displayed.
+     */
+    public void submitList(PagedList<T> pagedList) {
+        mDiffer.submitList(pagedList);
+    }
+
+    @Nullable
+    protected T getItem(int position) {
+        return mDiffer.getItem(position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mDiffer.getItemCount();
+    }
+
+    /**
+     * Returns the PagedList currently being displayed by the Adapter.
+     * <p>
+     * This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
+     * because a diff is computed asynchronously between the new list and the current list before
+     * updating the currentList value. May be null if no PagedList is being presented.
+     *
+     * @return The list currently being displayed.
+     */
+    @Nullable
+    public PagedList<T> getCurrentList() {
+        return mDiffer.getCurrentList();
+    }
+
+    /**
+     * Called when the current PagedList is updated.
+     * <p>
+     * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't
+     * needed (such as when the first list is passed, or the list is cleared). In either case,
+     * PagedListAdapter will simply call
+     * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}.
+     * <p>
+     * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList
+     * to a snapshot version of the PagedList during a diff. This means you cannot observe each
+     * PagedList via this method.
+     *
+     * @param currentList new PagedList being displayed, may be null.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
+    }
+}
diff --git a/paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.java b/paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.java
new file mode 100644
index 0000000..a2fd8a3
--- /dev/null
+++ b/paging/runtime/src/main/java/androidx/paging/PagedStorageDiffHelper.java
@@ -0,0 +1,173 @@
+/*
+ * 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 androidx.paging;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.ListUpdateCallback;
+
+class PagedStorageDiffHelper {
+    private PagedStorageDiffHelper() {
+    }
+
+    static <T> DiffUtil.DiffResult computeDiff(
+            final PagedStorage<T> oldList,
+            final PagedStorage<T> newList,
+            final DiffUtil.ItemCallback<T> diffCallback) {
+        final int oldOffset = oldList.computeLeadingNulls();
+        final int newOffset = newList.computeLeadingNulls();
+
+        final int oldSize = oldList.size() - oldOffset - oldList.computeTrailingNulls();
+        final int newSize = newList.size() - newOffset - newList.computeTrailingNulls();
+
+        return DiffUtil.calculateDiff(new DiffUtil.Callback() {
+            @Nullable
+            @Override
+            public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+                T oldItem = oldList.get(oldItemPosition + oldOffset);
+                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
+                if (oldItem == null || newItem == null) {
+                    return null;
+                }
+                return diffCallback.getChangePayload(oldItem, newItem);
+            }
+
+            @Override
+            public int getOldListSize() {
+                return oldSize;
+            }
+
+            @Override
+            public int getNewListSize() {
+                return newSize;
+            }
+
+            @Override
+            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+                T oldItem = oldList.get(oldItemPosition + oldOffset);
+                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
+                if (oldItem == newItem) {
+                    return true;
+                }
+                //noinspection SimplifiableIfStatement
+                if (oldItem == null || newItem == null) {
+                    return false;
+                }
+                return diffCallback.areItemsTheSame(oldItem, newItem);
+            }
+
+            @Override
+            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+                T oldItem = oldList.get(oldItemPosition + oldOffset);
+                T newItem = newList.get(newItemPosition + newList.getLeadingNullCount());
+                if (oldItem == newItem) {
+                    return true;
+                }
+                //noinspection SimplifiableIfStatement
+                if (oldItem == null || newItem == null) {
+                    return false;
+                }
+
+                return diffCallback.areContentsTheSame(oldItem, newItem);
+            }
+        }, true);
+    }
+
+    private static class OffsettingListUpdateCallback implements ListUpdateCallback {
+        private final int mOffset;
+        private final ListUpdateCallback mCallback;
+
+        private OffsettingListUpdateCallback(int offset, ListUpdateCallback callback) {
+            mOffset = offset;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onInserted(int position, int count) {
+            mCallback.onInserted(position + mOffset, count);
+        }
+
+        @Override
+        public void onRemoved(int position, int count) {
+            mCallback.onRemoved(position + mOffset, count);
+        }
+
+        @Override
+        public void onMoved(int fromPosition, int toPosition) {
+            mCallback.onRemoved(fromPosition + mOffset, toPosition + mOffset);
+        }
+
+        @Override
+        public void onChanged(int position, int count, Object payload) {
+            mCallback.onChanged(position + mOffset, count, payload);
+        }
+    }
+
+    /**
+     * TODO: improve diffing logic
+     *
+     * This function currently does a naive diff, assuming null does not become an item, and vice
+     * versa (so it won't dispatch onChange events for these). It's similar to passing a list with
+     * leading/trailing nulls in the beginning / end to DiffUtil, but dispatches the remove/insert
+     * for changed nulls at the beginning / end of the list.
+     *
+     * Note: if lists mutate between diffing the snapshot and dispatching the diff here, then we
+     * handle this by passing the snapshot to the callback, and dispatching those changes
+     * immediately after dispatching this diff.
+     */
+    static <T> void dispatchDiff(ListUpdateCallback callback,
+            final PagedStorage<T> oldList,
+            final PagedStorage<T> newList,
+            final DiffUtil.DiffResult diffResult) {
+
+        final int trailingOld = oldList.computeTrailingNulls();
+        final int trailingNew = newList.computeTrailingNulls();
+        final int leadingOld = oldList.computeLeadingNulls();
+        final int leadingNew = newList.computeLeadingNulls();
+
+        if (trailingOld == 0
+                && trailingNew == 0
+                && leadingOld == 0
+                && leadingNew == 0) {
+            // Simple case, dispatch & return
+            diffResult.dispatchUpdatesTo(callback);
+            return;
+        }
+
+        // First, remove or insert trailing nulls
+        if (trailingOld > trailingNew) {
+            int count = trailingOld - trailingNew;
+            callback.onRemoved(oldList.size() - count, count);
+        } else if (trailingOld < trailingNew) {
+            callback.onInserted(oldList.size(), trailingNew - trailingOld);
+        }
+
+        // Second, remove or insert leading nulls
+        if (leadingOld > leadingNew) {
+            callback.onRemoved(0, leadingOld - leadingNew);
+        } else if (leadingOld < leadingNew) {
+            callback.onInserted(0, leadingNew - leadingOld);
+        }
+
+        // apply the diff, with an offset if needed
+        if (leadingNew != 0) {
+            diffResult.dispatchUpdatesTo(new OffsettingListUpdateCallback(leadingNew, callback));
+        } else {
+            diffResult.dispatchUpdatesTo(callback);
+        }
+    }
+}
diff --git a/persistence/db-framework/api/current.txt b/persistence/db-framework/api/current.txt
index 7051765..b784c5f 100644
--- a/persistence/db-framework/api/current.txt
+++ b/persistence/db-framework/api/current.txt
@@ -1,8 +1,8 @@
-package android.arch.persistence.db.framework {
+package androidx.sqlite.db.framework {
 
-  public final class FrameworkSQLiteOpenHelperFactory implements android.arch.persistence.db.SupportSQLiteOpenHelper.Factory {
+  public final class FrameworkSQLiteOpenHelperFactory implements androidx.sqlite.db.SupportSQLiteOpenHelper.Factory {
     ctor public FrameworkSQLiteOpenHelperFactory();
-    method public android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
   }
 
 }
diff --git a/persistence/db-framework/api/1.0.0.txt b/persistence/db-framework/api_legacy/1.0.0.txt
similarity index 100%
rename from persistence/db-framework/api/1.0.0.txt
rename to persistence/db-framework/api_legacy/1.0.0.txt
diff --git a/persistence/db-framework/api/1.0.0.txt b/persistence/db-framework/api_legacy/1.1.0.txt
similarity index 100%
copy from persistence/db-framework/api/1.0.0.txt
copy to persistence/db-framework/api_legacy/1.1.0.txt
diff --git a/persistence/db-framework/api/1.0.0.txt b/persistence/db-framework/api_legacy/current.txt
similarity index 100%
copy from persistence/db-framework/api/1.0.0.txt
copy to persistence/db-framework/api_legacy/current.txt
diff --git a/persistence/db-framework/build.gradle b/persistence/db-framework/build.gradle
index 15f638b..eb599de 100644
--- a/persistence/db-framework/build.gradle
+++ b/persistence/db-framework/build.gradle
@@ -25,7 +25,7 @@
 
 dependencies {
     api(SUPPORT_ANNOTATIONS)
-    api(project(":persistence:db"))
+    api(project(":sqlite:sqlite"))
 }
 
 supportLibrary {
diff --git a/persistence/db-framework/src/main/AndroidManifest.xml b/persistence/db-framework/src/main/AndroidManifest.xml
index 9350b90..f18f9ad 100644
--- a/persistence/db-framework/src/main/AndroidManifest.xml
+++ b/persistence/db-framework/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.persistence.db.framework">
+          package="androidx.sqlite.db.framework">
 </manifest>
diff --git a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java b/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
deleted file mode 100644
index d564a03..0000000
--- a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db.framework;
-
-import static android.text.TextUtils.isEmpty;
-
-import android.arch.persistence.db.SimpleSQLiteQuery;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteCursor;
-import android.database.sqlite.SQLiteCursorDriver;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQuery;
-import android.database.sqlite.SQLiteTransactionListener;
-import android.os.Build;
-import android.os.CancellationSignal;
-import android.support.annotation.RequiresApi;
-import android.util.Pair;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Delegates all calls to an implementation of {@link SQLiteDatabase}.
- */
-@SuppressWarnings("unused")
-class FrameworkSQLiteDatabase implements SupportSQLiteDatabase {
-    private static final String[] CONFLICT_VALUES = new String[]
-            {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
-    private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
-    private final SQLiteDatabase mDelegate;
-
-    /**
-     * Creates a wrapper around {@link SQLiteDatabase}.
-     *
-     * @param delegate The delegate to receive all calls.
-     */
-    FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
-        mDelegate = delegate;
-    }
-
-    @Override
-    public SupportSQLiteStatement compileStatement(String sql) {
-        return new FrameworkSQLiteStatement(mDelegate.compileStatement(sql));
-    }
-
-    @Override
-    public void beginTransaction() {
-        mDelegate.beginTransaction();
-    }
-
-    @Override
-    public void beginTransactionNonExclusive() {
-        mDelegate.beginTransactionNonExclusive();
-    }
-
-    @Override
-    public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
-        mDelegate.beginTransactionWithListener(transactionListener);
-    }
-
-    @Override
-    public void beginTransactionWithListenerNonExclusive(
-            SQLiteTransactionListener transactionListener) {
-        mDelegate.beginTransactionWithListenerNonExclusive(transactionListener);
-    }
-
-    @Override
-    public void endTransaction() {
-        mDelegate.endTransaction();
-    }
-
-    @Override
-    public void setTransactionSuccessful() {
-        mDelegate.setTransactionSuccessful();
-    }
-
-    @Override
-    public boolean inTransaction() {
-        return mDelegate.inTransaction();
-    }
-
-    @Override
-    public boolean isDbLockedByCurrentThread() {
-        return mDelegate.isDbLockedByCurrentThread();
-    }
-
-    @Override
-    public boolean yieldIfContendedSafely() {
-        return mDelegate.yieldIfContendedSafely();
-    }
-
-    @Override
-    public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
-        return mDelegate.yieldIfContendedSafely(sleepAfterYieldDelay);
-    }
-
-    @Override
-    public int getVersion() {
-        return mDelegate.getVersion();
-    }
-
-    @Override
-    public void setVersion(int version) {
-        mDelegate.setVersion(version);
-    }
-
-    @Override
-    public long getMaximumSize() {
-        return mDelegate.getMaximumSize();
-    }
-
-    @Override
-    public long setMaximumSize(long numBytes) {
-        return mDelegate.setMaximumSize(numBytes);
-    }
-
-    @Override
-    public long getPageSize() {
-        return mDelegate.getPageSize();
-    }
-
-    @Override
-    public void setPageSize(long numBytes) {
-        mDelegate.setPageSize(numBytes);
-    }
-
-    @Override
-    public Cursor query(String query) {
-        return query(new SimpleSQLiteQuery(query));
-    }
-
-    @Override
-    public Cursor query(String query, Object[] bindArgs) {
-        return query(new SimpleSQLiteQuery(query, bindArgs));
-    }
-
-
-    @Override
-    public Cursor query(final SupportSQLiteQuery supportQuery) {
-        return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
-            @Override
-            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
-                    String editTable, SQLiteQuery query) {
-                supportQuery.bindTo(new FrameworkSQLiteProgram(query));
-                return new SQLiteCursor(masterQuery, editTable, query);
-            }
-        }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null);
-    }
-
-    @Override
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    public Cursor query(final SupportSQLiteQuery supportQuery,
-            CancellationSignal cancellationSignal) {
-        return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
-            @Override
-            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
-                    String editTable, SQLiteQuery query) {
-                supportQuery.bindTo(new FrameworkSQLiteProgram(query));
-                return new SQLiteCursor(masterQuery, editTable, query);
-            }
-        }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null, cancellationSignal);
-    }
-
-    @Override
-    public long insert(String table, int conflictAlgorithm, ContentValues values)
-            throws SQLException {
-        return mDelegate.insertWithOnConflict(table, null, values,
-                conflictAlgorithm);
-    }
-
-    @Override
-    public int delete(String table, String whereClause, Object[] whereArgs) {
-        String query = "DELETE FROM " + table
-                + (isEmpty(whereClause) ? "" : " WHERE " + whereClause);
-        SupportSQLiteStatement statement = compileStatement(query);
-        SimpleSQLiteQuery.bind(statement, whereArgs);
-        return statement.executeUpdateDelete();
-    }
-
-
-    @Override
-    public int update(String table, int conflictAlgorithm, ContentValues values, String whereClause,
-            Object[] whereArgs) {
-        // taken from SQLiteDatabase class.
-        if (values == null || values.size() == 0) {
-            throw new IllegalArgumentException("Empty values");
-        }
-        StringBuilder sql = new StringBuilder(120);
-        sql.append("UPDATE ");
-        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
-        sql.append(table);
-        sql.append(" SET ");
-
-        // move all bind args to one array
-        int setValuesSize = values.size();
-        int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
-        Object[] bindArgs = new Object[bindArgsSize];
-        int i = 0;
-        for (String colName : values.keySet()) {
-            sql.append((i > 0) ? "," : "");
-            sql.append(colName);
-            bindArgs[i++] = values.get(colName);
-            sql.append("=?");
-        }
-        if (whereArgs != null) {
-            for (i = setValuesSize; i < bindArgsSize; i++) {
-                bindArgs[i] = whereArgs[i - setValuesSize];
-            }
-        }
-        if (!isEmpty(whereClause)) {
-            sql.append(" WHERE ");
-            sql.append(whereClause);
-        }
-        SupportSQLiteStatement stmt = compileStatement(sql.toString());
-        SimpleSQLiteQuery.bind(stmt, bindArgs);
-        return stmt.executeUpdateDelete();
-    }
-
-    @Override
-    public void execSQL(String sql) throws SQLException {
-        mDelegate.execSQL(sql);
-    }
-
-    @Override
-    public void execSQL(String sql, Object[] bindArgs) throws SQLException {
-        mDelegate.execSQL(sql, bindArgs);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return mDelegate.isReadOnly();
-    }
-
-    @Override
-    public boolean isOpen() {
-        return mDelegate.isOpen();
-    }
-
-    @Override
-    public boolean needUpgrade(int newVersion) {
-        return mDelegate.needUpgrade(newVersion);
-    }
-
-    @Override
-    public String getPath() {
-        return mDelegate.getPath();
-    }
-
-    @Override
-    public void setLocale(Locale locale) {
-        mDelegate.setLocale(locale);
-    }
-
-    @Override
-    public void setMaxSqlCacheSize(int cacheSize) {
-        mDelegate.setMaxSqlCacheSize(cacheSize);
-    }
-
-    @Override
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    public void setForeignKeyConstraintsEnabled(boolean enable) {
-        mDelegate.setForeignKeyConstraintsEnabled(enable);
-    }
-
-    @Override
-    public boolean enableWriteAheadLogging() {
-        return mDelegate.enableWriteAheadLogging();
-    }
-
-    @Override
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    public void disableWriteAheadLogging() {
-        mDelegate.disableWriteAheadLogging();
-    }
-
-    @Override
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    public boolean isWriteAheadLoggingEnabled() {
-        return mDelegate.isWriteAheadLoggingEnabled();
-    }
-
-    @Override
-    public List<Pair<String, String>> getAttachedDbs() {
-        return mDelegate.getAttachedDbs();
-    }
-
-    @Override
-    public boolean isDatabaseIntegrityOk() {
-        return mDelegate.isDatabaseIntegrityOk();
-    }
-
-    @Override
-    public void close() throws IOException {
-        mDelegate.close();
-    }
-}
diff --git a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java b/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
deleted file mode 100644
index a1690f4..0000000
--- a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db.framework;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.content.Context;
-import android.database.DatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Build;
-import android.support.annotation.RequiresApi;
-
-class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
-    private final OpenHelper mDelegate;
-
-    FrameworkSQLiteOpenHelper(Context context, String name,
-            Callback callback) {
-        mDelegate = createDelegate(context, name, callback);
-    }
-
-    private OpenHelper createDelegate(Context context, String name, Callback callback) {
-        final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
-        return new OpenHelper(context, name, dbRef, callback);
-    }
-
-    @Override
-    public String getDatabaseName() {
-        return mDelegate.getDatabaseName();
-    }
-
-    @Override
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    public void setWriteAheadLoggingEnabled(boolean enabled) {
-        mDelegate.setWriteAheadLoggingEnabled(enabled);
-    }
-
-    @Override
-    public SupportSQLiteDatabase getWritableDatabase() {
-        return mDelegate.getWritableSupportDatabase();
-    }
-
-    @Override
-    public SupportSQLiteDatabase getReadableDatabase() {
-        return mDelegate.getReadableSupportDatabase();
-    }
-
-    @Override
-    public void close() {
-        mDelegate.close();
-    }
-
-    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;
-
-        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() {
-            SQLiteDatabase db = super.getWritableDatabase();
-            return getWrappedDb(db);
-        }
-
-        SupportSQLiteDatabase getReadableSupportDatabase() {
-            SQLiteDatabase db = super.getReadableDatabase();
-            return getWrappedDb(db);
-        }
-
-        FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
-            FrameworkSQLiteDatabase dbRef = mDbRef[0];
-            if (dbRef == null) {
-                dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
-                mDbRef[0] = dbRef;
-            }
-            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();
-            mDbRef[0] = null;
-        }
-    }
-}
diff --git a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
deleted file mode 100644
index ab11d49..0000000
--- a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db.framework;
-
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-
-/**
- * Implements {@link SupportSQLiteOpenHelper.Factory} using the SQLite implementation in the
- * framework.
- */
-@SuppressWarnings("unused")
-public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
-    @Override
-    public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
-        return new FrameworkSQLiteOpenHelper(
-                configuration.context, configuration.name, configuration.callback);
-    }
-}
diff --git a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java b/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java
deleted file mode 100644
index 73c98c6..0000000
--- a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteProgram.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db.framework;
-
-import android.arch.persistence.db.SupportSQLiteProgram;
-import android.database.sqlite.SQLiteProgram;
-
-/**
- * An wrapper around {@link SQLiteProgram} to implement {@link SupportSQLiteProgram} API.
- */
-class FrameworkSQLiteProgram implements SupportSQLiteProgram {
-    private final SQLiteProgram mDelegate;
-
-    FrameworkSQLiteProgram(SQLiteProgram delegate) {
-        mDelegate = delegate;
-    }
-
-    @Override
-    public void bindNull(int index) {
-        mDelegate.bindNull(index);
-    }
-
-    @Override
-    public void bindLong(int index, long value) {
-        mDelegate.bindLong(index, value);
-    }
-
-    @Override
-    public void bindDouble(int index, double value) {
-        mDelegate.bindDouble(index, value);
-    }
-
-    @Override
-    public void bindString(int index, String value) {
-        mDelegate.bindString(index, value);
-    }
-
-    @Override
-    public void bindBlob(int index, byte[] value) {
-        mDelegate.bindBlob(index, value);
-    }
-
-    @Override
-    public void clearBindings() {
-        mDelegate.clearBindings();
-    }
-
-    @Override
-    public void close() {
-        mDelegate.close();
-    }
-}
diff --git a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java b/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
deleted file mode 100644
index 7f07865..0000000
--- a/persistence/db-framework/src/main/java/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db.framework;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.database.sqlite.SQLiteStatement;
-
-/**
- * Delegates all calls to a {@link SQLiteStatement}.
- */
-class FrameworkSQLiteStatement extends FrameworkSQLiteProgram implements SupportSQLiteStatement {
-    private final SQLiteStatement mDelegate;
-
-    /**
-     * Creates a wrapper around a framework {@link SQLiteStatement}.
-     *
-     * @param delegate The SQLiteStatement to delegate calls to.
-     */
-    FrameworkSQLiteStatement(SQLiteStatement delegate) {
-        super(delegate);
-        mDelegate = delegate;
-    }
-
-    @Override
-    public void execute() {
-        mDelegate.execute();
-    }
-
-    @Override
-    public int executeUpdateDelete() {
-        return mDelegate.executeUpdateDelete();
-    }
-
-    @Override
-    public long executeInsert() {
-        return mDelegate.executeInsert();
-    }
-
-    @Override
-    public long simpleQueryForLong() {
-        return mDelegate.simpleQueryForLong();
-    }
-
-    @Override
-    public String simpleQueryForString() {
-        return mDelegate.simpleQueryForString();
-    }
-}
diff --git a/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.java b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.java
new file mode 100644
index 0000000..15a4a2e
--- /dev/null
+++ b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db.framework;
+
+import static android.text.TextUtils.isEmpty;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQuery;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.util.Pair;
+
+import androidx.annotation.RequiresApi;
+import androidx.sqlite.db.SimpleSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Delegates all calls to an implementation of {@link SQLiteDatabase}.
+ */
+@SuppressWarnings("unused")
+class FrameworkSQLiteDatabase implements SupportSQLiteDatabase {
+    private static final String[] CONFLICT_VALUES = new String[]
+            {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+    private final SQLiteDatabase mDelegate;
+
+    /**
+     * Creates a wrapper around {@link SQLiteDatabase}.
+     *
+     * @param delegate The delegate to receive all calls.
+     */
+    FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public SupportSQLiteStatement compileStatement(String sql) {
+        return new FrameworkSQLiteStatement(mDelegate.compileStatement(sql));
+    }
+
+    @Override
+    public void beginTransaction() {
+        mDelegate.beginTransaction();
+    }
+
+    @Override
+    public void beginTransactionNonExclusive() {
+        mDelegate.beginTransactionNonExclusive();
+    }
+
+    @Override
+    public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+        mDelegate.beginTransactionWithListener(transactionListener);
+    }
+
+    @Override
+    public void beginTransactionWithListenerNonExclusive(
+            SQLiteTransactionListener transactionListener) {
+        mDelegate.beginTransactionWithListenerNonExclusive(transactionListener);
+    }
+
+    @Override
+    public void endTransaction() {
+        mDelegate.endTransaction();
+    }
+
+    @Override
+    public void setTransactionSuccessful() {
+        mDelegate.setTransactionSuccessful();
+    }
+
+    @Override
+    public boolean inTransaction() {
+        return mDelegate.inTransaction();
+    }
+
+    @Override
+    public boolean isDbLockedByCurrentThread() {
+        return mDelegate.isDbLockedByCurrentThread();
+    }
+
+    @Override
+    public boolean yieldIfContendedSafely() {
+        return mDelegate.yieldIfContendedSafely();
+    }
+
+    @Override
+    public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
+        return mDelegate.yieldIfContendedSafely(sleepAfterYieldDelay);
+    }
+
+    @Override
+    public int getVersion() {
+        return mDelegate.getVersion();
+    }
+
+    @Override
+    public void setVersion(int version) {
+        mDelegate.setVersion(version);
+    }
+
+    @Override
+    public long getMaximumSize() {
+        return mDelegate.getMaximumSize();
+    }
+
+    @Override
+    public long setMaximumSize(long numBytes) {
+        return mDelegate.setMaximumSize(numBytes);
+    }
+
+    @Override
+    public long getPageSize() {
+        return mDelegate.getPageSize();
+    }
+
+    @Override
+    public void setPageSize(long numBytes) {
+        mDelegate.setPageSize(numBytes);
+    }
+
+    @Override
+    public Cursor query(String query) {
+        return query(new SimpleSQLiteQuery(query));
+    }
+
+    @Override
+    public Cursor query(String query, Object[] bindArgs) {
+        return query(new SimpleSQLiteQuery(query, bindArgs));
+    }
+
+
+    @Override
+    public Cursor query(final SupportSQLiteQuery supportQuery) {
+        return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
+            @Override
+            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
+                    String editTable, SQLiteQuery query) {
+                supportQuery.bindTo(new FrameworkSQLiteProgram(query));
+                return new SQLiteCursor(masterQuery, editTable, query);
+            }
+        }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null);
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public Cursor query(final SupportSQLiteQuery supportQuery,
+            CancellationSignal cancellationSignal) {
+        return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
+            @Override
+            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
+                    String editTable, SQLiteQuery query) {
+                supportQuery.bindTo(new FrameworkSQLiteProgram(query));
+                return new SQLiteCursor(masterQuery, editTable, query);
+            }
+        }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null, cancellationSignal);
+    }
+
+    @Override
+    public long insert(String table, int conflictAlgorithm, ContentValues values)
+            throws SQLException {
+        return mDelegate.insertWithOnConflict(table, null, values,
+                conflictAlgorithm);
+    }
+
+    @Override
+    public int delete(String table, String whereClause, Object[] whereArgs) {
+        String query = "DELETE FROM " + table
+                + (isEmpty(whereClause) ? "" : " WHERE " + whereClause);
+        SupportSQLiteStatement statement = compileStatement(query);
+        SimpleSQLiteQuery.bind(statement, whereArgs);
+        return statement.executeUpdateDelete();
+    }
+
+
+    @Override
+    public int update(String table, int conflictAlgorithm, ContentValues values, String whereClause,
+            Object[] whereArgs) {
+        // taken from SQLiteDatabase class.
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("Empty values");
+        }
+        StringBuilder sql = new StringBuilder(120);
+        sql.append("UPDATE ");
+        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
+        sql.append(table);
+        sql.append(" SET ");
+
+        // move all bind args to one array
+        int setValuesSize = values.size();
+        int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
+        Object[] bindArgs = new Object[bindArgsSize];
+        int i = 0;
+        for (String colName : values.keySet()) {
+            sql.append((i > 0) ? "," : "");
+            sql.append(colName);
+            bindArgs[i++] = values.get(colName);
+            sql.append("=?");
+        }
+        if (whereArgs != null) {
+            for (i = setValuesSize; i < bindArgsSize; i++) {
+                bindArgs[i] = whereArgs[i - setValuesSize];
+            }
+        }
+        if (!isEmpty(whereClause)) {
+            sql.append(" WHERE ");
+            sql.append(whereClause);
+        }
+        SupportSQLiteStatement stmt = compileStatement(sql.toString());
+        SimpleSQLiteQuery.bind(stmt, bindArgs);
+        return stmt.executeUpdateDelete();
+    }
+
+    @Override
+    public void execSQL(String sql) throws SQLException {
+        mDelegate.execSQL(sql);
+    }
+
+    @Override
+    public void execSQL(String sql, Object[] bindArgs) throws SQLException {
+        mDelegate.execSQL(sql, bindArgs);
+    }
+
+    @Override
+    public boolean isReadOnly() {
+        return mDelegate.isReadOnly();
+    }
+
+    @Override
+    public boolean isOpen() {
+        return mDelegate.isOpen();
+    }
+
+    @Override
+    public boolean needUpgrade(int newVersion) {
+        return mDelegate.needUpgrade(newVersion);
+    }
+
+    @Override
+    public String getPath() {
+        return mDelegate.getPath();
+    }
+
+    @Override
+    public void setLocale(Locale locale) {
+        mDelegate.setLocale(locale);
+    }
+
+    @Override
+    public void setMaxSqlCacheSize(int cacheSize) {
+        mDelegate.setMaxSqlCacheSize(cacheSize);
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void setForeignKeyConstraintsEnabled(boolean enable) {
+        mDelegate.setForeignKeyConstraintsEnabled(enable);
+    }
+
+    @Override
+    public boolean enableWriteAheadLogging() {
+        return mDelegate.enableWriteAheadLogging();
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void disableWriteAheadLogging() {
+        mDelegate.disableWriteAheadLogging();
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public boolean isWriteAheadLoggingEnabled() {
+        return mDelegate.isWriteAheadLoggingEnabled();
+    }
+
+    @Override
+    public List<Pair<String, String>> getAttachedDbs() {
+        return mDelegate.getAttachedDbs();
+    }
+
+    @Override
+    public boolean isDatabaseIntegrityOk() {
+        return mDelegate.isDatabaseIntegrityOk();
+    }
+
+    @Override
+    public void close() throws IOException {
+        mDelegate.close();
+    }
+}
diff --git a/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
new file mode 100644
index 0000000..9ae374e
--- /dev/null
+++ b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db.framework;
+
+import android.content.Context;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
+    private final OpenHelper mDelegate;
+
+    FrameworkSQLiteOpenHelper(Context context, String name,
+            Callback callback) {
+        mDelegate = createDelegate(context, name, callback);
+    }
+
+    private OpenHelper createDelegate(Context context, String name, Callback callback) {
+        final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
+        return new OpenHelper(context, name, dbRef, callback);
+    }
+
+    @Override
+    public String getDatabaseName() {
+        return mDelegate.getDatabaseName();
+    }
+
+    @Override
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void setWriteAheadLoggingEnabled(boolean enabled) {
+        mDelegate.setWriteAheadLoggingEnabled(enabled);
+    }
+
+    @Override
+    public SupportSQLiteDatabase getWritableDatabase() {
+        return mDelegate.getWritableSupportDatabase();
+    }
+
+    @Override
+    public SupportSQLiteDatabase getReadableDatabase() {
+        return mDelegate.getReadableSupportDatabase();
+    }
+
+    @Override
+    public void close() {
+        mDelegate.close();
+    }
+
+    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;
+
+        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() {
+            SQLiteDatabase db = super.getWritableDatabase();
+            return getWrappedDb(db);
+        }
+
+        SupportSQLiteDatabase getReadableSupportDatabase() {
+            SQLiteDatabase db = super.getReadableDatabase();
+            return getWrappedDb(db);
+        }
+
+        FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
+            FrameworkSQLiteDatabase dbRef = mDbRef[0];
+            if (dbRef == null) {
+                dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
+                mDbRef[0] = dbRef;
+            }
+            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();
+            mDbRef[0] = null;
+        }
+    }
+}
diff --git a/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
new file mode 100644
index 0000000..e65fabe
--- /dev/null
+++ b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db.framework;
+
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+/**
+ * Implements {@link SupportSQLiteOpenHelper.Factory} using the SQLite implementation in the
+ * framework.
+ */
+@SuppressWarnings("unused")
+public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
+    @Override
+    public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
+        return new FrameworkSQLiteOpenHelper(
+                configuration.context, configuration.name, configuration.callback);
+    }
+}
diff --git a/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteProgram.java b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteProgram.java
new file mode 100644
index 0000000..d352d56
--- /dev/null
+++ b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteProgram.java
@@ -0,0 +1,67 @@
+/*
+ * 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 androidx.sqlite.db.framework;
+
+import android.database.sqlite.SQLiteProgram;
+
+import androidx.sqlite.db.SupportSQLiteProgram;
+
+/**
+ * An wrapper around {@link SQLiteProgram} to implement {@link SupportSQLiteProgram} API.
+ */
+class FrameworkSQLiteProgram implements SupportSQLiteProgram {
+    private final SQLiteProgram mDelegate;
+
+    FrameworkSQLiteProgram(SQLiteProgram delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void bindNull(int index) {
+        mDelegate.bindNull(index);
+    }
+
+    @Override
+    public void bindLong(int index, long value) {
+        mDelegate.bindLong(index, value);
+    }
+
+    @Override
+    public void bindDouble(int index, double value) {
+        mDelegate.bindDouble(index, value);
+    }
+
+    @Override
+    public void bindString(int index, String value) {
+        mDelegate.bindString(index, value);
+    }
+
+    @Override
+    public void bindBlob(int index, byte[] value) {
+        mDelegate.bindBlob(index, value);
+    }
+
+    @Override
+    public void clearBindings() {
+        mDelegate.clearBindings();
+    }
+
+    @Override
+    public void close() {
+        mDelegate.close();
+    }
+}
diff --git a/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteStatement.java b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteStatement.java
new file mode 100644
index 0000000..9ef2fa7
--- /dev/null
+++ b/persistence/db-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteStatement.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db.framework;
+
+import android.database.sqlite.SQLiteStatement;
+
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+/**
+ * Delegates all calls to a {@link SQLiteStatement}.
+ */
+class FrameworkSQLiteStatement extends FrameworkSQLiteProgram implements SupportSQLiteStatement {
+    private final SQLiteStatement mDelegate;
+
+    /**
+     * Creates a wrapper around a framework {@link SQLiteStatement}.
+     *
+     * @param delegate The SQLiteStatement to delegate calls to.
+     */
+    FrameworkSQLiteStatement(SQLiteStatement delegate) {
+        super(delegate);
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void execute() {
+        mDelegate.execute();
+    }
+
+    @Override
+    public int executeUpdateDelete() {
+        return mDelegate.executeUpdateDelete();
+    }
+
+    @Override
+    public long executeInsert() {
+        return mDelegate.executeInsert();
+    }
+
+    @Override
+    public long simpleQueryForLong() {
+        return mDelegate.simpleQueryForLong();
+    }
+
+    @Override
+    public String simpleQueryForString() {
+        return mDelegate.simpleQueryForString();
+    }
+}
diff --git a/persistence/db/api/current.txt b/persistence/db/api/current.txt
index e0abe20..270d88c 100644
--- a/persistence/db/api/current.txt
+++ b/persistence/db/api/current.txt
@@ -1,10 +1,10 @@
-package android.arch.persistence.db {
+package androidx.sqlite.db {
 
-  public final class SimpleSQLiteQuery implements android.arch.persistence.db.SupportSQLiteQuery {
+  public final class SimpleSQLiteQuery implements androidx.sqlite.db.SupportSQLiteQuery {
     ctor public SimpleSQLiteQuery(java.lang.String, java.lang.Object[]);
     ctor public SimpleSQLiteQuery(java.lang.String);
-    method public static void bind(android.arch.persistence.db.SupportSQLiteProgram, java.lang.Object[]);
-    method public void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+    method public static void bind(androidx.sqlite.db.SupportSQLiteProgram, java.lang.Object[]);
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram);
     method public int getArgCount();
     method public java.lang.String getSql();
   }
@@ -14,7 +14,7 @@
     method public abstract void beginTransactionNonExclusive();
     method public abstract void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener);
     method public abstract void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
-    method public abstract android.arch.persistence.db.SupportSQLiteStatement compileStatement(java.lang.String);
+    method public abstract androidx.sqlite.db.SupportSQLiteStatement compileStatement(java.lang.String);
     method public abstract int delete(java.lang.String, java.lang.String, java.lang.Object[]);
     method public abstract void disableWriteAheadLogging();
     method public abstract boolean enableWriteAheadLogging();
@@ -36,8 +36,8 @@
     method public abstract boolean needUpgrade(int);
     method public abstract android.database.Cursor query(java.lang.String);
     method public abstract android.database.Cursor query(java.lang.String, java.lang.Object[]);
-    method public abstract android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery);
-    method public abstract android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery, android.os.CancellationSignal);
+    method public abstract android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+    method public abstract android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal);
     method public abstract void setForeignKeyConstraintsEnabled(boolean);
     method public abstract void setLocale(java.util.Locale);
     method public abstract void setMaxSqlCacheSize(int);
@@ -53,37 +53,37 @@
   public abstract interface SupportSQLiteOpenHelper {
     method public abstract void close();
     method public abstract java.lang.String getDatabaseName();
-    method public abstract android.arch.persistence.db.SupportSQLiteDatabase getReadableDatabase();
-    method public abstract android.arch.persistence.db.SupportSQLiteDatabase getWritableDatabase();
+    method public abstract androidx.sqlite.db.SupportSQLiteDatabase getReadableDatabase();
+    method public abstract androidx.sqlite.db.SupportSQLiteDatabase getWritableDatabase();
     method public abstract void setWriteAheadLoggingEnabled(boolean);
   }
 
   public static abstract class SupportSQLiteOpenHelper.Callback {
     ctor public SupportSQLiteOpenHelper.Callback(int);
-    method public void onConfigure(android.arch.persistence.db.SupportSQLiteDatabase);
-    method public void onCorruption(android.arch.persistence.db.SupportSQLiteDatabase);
-    method public abstract void onCreate(android.arch.persistence.db.SupportSQLiteDatabase);
-    method public void onDowngrade(android.arch.persistence.db.SupportSQLiteDatabase, int, int);
-    method public void onOpen(android.arch.persistence.db.SupportSQLiteDatabase);
-    method public abstract void onUpgrade(android.arch.persistence.db.SupportSQLiteDatabase, int, int);
+    method public void onConfigure(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onCorruption(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDowngrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
     field public final int version;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration {
-    method public static android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
-    field public final android.arch.persistence.db.SupportSQLiteOpenHelper.Callback callback;
+    method public static androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
     field public final android.content.Context context;
     field public final java.lang.String name;
   }
 
   public static class SupportSQLiteOpenHelper.Configuration.Builder {
-    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration build();
-    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder callback(android.arch.persistence.db.SupportSQLiteOpenHelper.Callback);
-    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder name(java.lang.String);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(java.lang.String);
   }
 
   public static abstract interface SupportSQLiteOpenHelper.Factory {
-    method public abstract android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
+    method public abstract androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
   }
 
   public abstract interface SupportSQLiteProgram implements java.io.Closeable {
@@ -96,24 +96,24 @@
   }
 
   public abstract interface SupportSQLiteQuery {
-    method public abstract void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+    method public abstract void bindTo(androidx.sqlite.db.SupportSQLiteProgram);
     method public abstract int getArgCount();
     method public abstract java.lang.String getSql();
   }
 
   public final class SupportSQLiteQueryBuilder {
-    method public static android.arch.persistence.db.SupportSQLiteQueryBuilder builder(java.lang.String);
-    method public android.arch.persistence.db.SupportSQLiteQueryBuilder columns(java.lang.String[]);
-    method public android.arch.persistence.db.SupportSQLiteQuery create();
-    method public android.arch.persistence.db.SupportSQLiteQueryBuilder distinct();
-    method public android.arch.persistence.db.SupportSQLiteQueryBuilder groupBy(java.lang.String);
-    method public android.arch.persistence.db.SupportSQLiteQueryBuilder having(java.lang.String);
-    method public android.arch.persistence.db.SupportSQLiteQueryBuilder limit(java.lang.String);
-    method public android.arch.persistence.db.SupportSQLiteQueryBuilder orderBy(java.lang.String);
-    method public android.arch.persistence.db.SupportSQLiteQueryBuilder selection(java.lang.String, java.lang.Object[]);
+    method public static androidx.sqlite.db.SupportSQLiteQueryBuilder builder(java.lang.String);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder columns(java.lang.String[]);
+    method public androidx.sqlite.db.SupportSQLiteQuery create();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder distinct();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder groupBy(java.lang.String);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder having(java.lang.String);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder limit(java.lang.String);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder orderBy(java.lang.String);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder selection(java.lang.String, java.lang.Object[]);
   }
 
-  public abstract interface SupportSQLiteStatement implements android.arch.persistence.db.SupportSQLiteProgram {
+  public abstract interface SupportSQLiteStatement implements androidx.sqlite.db.SupportSQLiteProgram {
     method public abstract void execute();
     method public abstract long executeInsert();
     method public abstract int executeUpdateDelete();
diff --git a/persistence/db/api/1.0.0.txt b/persistence/db/api_legacy/1.0.0.txt
similarity index 100%
rename from persistence/db/api/1.0.0.txt
rename to persistence/db/api_legacy/1.0.0.txt
diff --git a/persistence/db/api_legacy/1.1.0.txt b/persistence/db/api_legacy/1.1.0.txt
new file mode 100644
index 0000000..e0abe20
--- /dev/null
+++ b/persistence/db/api_legacy/1.1.0.txt
@@ -0,0 +1,125 @@
+package android.arch.persistence.db {
+
+  public final class SimpleSQLiteQuery implements android.arch.persistence.db.SupportSQLiteQuery {
+    ctor public SimpleSQLiteQuery(java.lang.String, java.lang.Object[]);
+    ctor public SimpleSQLiteQuery(java.lang.String);
+    method public static void bind(android.arch.persistence.db.SupportSQLiteProgram, java.lang.Object[]);
+    method public void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+    method public int getArgCount();
+    method public java.lang.String getSql();
+  }
+
+  public abstract interface SupportSQLiteDatabase implements java.io.Closeable {
+    method public abstract void beginTransaction();
+    method public abstract void beginTransactionNonExclusive();
+    method public abstract void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener);
+    method public abstract void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
+    method public abstract android.arch.persistence.db.SupportSQLiteStatement compileStatement(java.lang.String);
+    method public abstract int delete(java.lang.String, java.lang.String, java.lang.Object[]);
+    method public abstract void disableWriteAheadLogging();
+    method public abstract boolean enableWriteAheadLogging();
+    method public abstract void endTransaction();
+    method public abstract void execSQL(java.lang.String) throws android.database.SQLException;
+    method public abstract void execSQL(java.lang.String, java.lang.Object[]) throws android.database.SQLException;
+    method public abstract java.util.List<android.util.Pair<java.lang.String, java.lang.String>> getAttachedDbs();
+    method public abstract long getMaximumSize();
+    method public abstract long getPageSize();
+    method public abstract java.lang.String getPath();
+    method public abstract int getVersion();
+    method public abstract boolean inTransaction();
+    method public abstract long insert(java.lang.String, int, android.content.ContentValues) throws android.database.SQLException;
+    method public abstract boolean isDatabaseIntegrityOk();
+    method public abstract boolean isDbLockedByCurrentThread();
+    method public abstract boolean isOpen();
+    method public abstract boolean isReadOnly();
+    method public abstract boolean isWriteAheadLoggingEnabled();
+    method public abstract boolean needUpgrade(int);
+    method public abstract android.database.Cursor query(java.lang.String);
+    method public abstract android.database.Cursor query(java.lang.String, java.lang.Object[]);
+    method public abstract android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery);
+    method public abstract android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery, android.os.CancellationSignal);
+    method public abstract void setForeignKeyConstraintsEnabled(boolean);
+    method public abstract void setLocale(java.util.Locale);
+    method public abstract void setMaxSqlCacheSize(int);
+    method public abstract long setMaximumSize(long);
+    method public abstract void setPageSize(long);
+    method public abstract void setTransactionSuccessful();
+    method public abstract void setVersion(int);
+    method public abstract int update(java.lang.String, int, android.content.ContentValues, java.lang.String, java.lang.Object[]);
+    method public abstract boolean yieldIfContendedSafely();
+    method public abstract boolean yieldIfContendedSafely(long);
+  }
+
+  public abstract interface SupportSQLiteOpenHelper {
+    method public abstract void close();
+    method public abstract java.lang.String getDatabaseName();
+    method public abstract android.arch.persistence.db.SupportSQLiteDatabase getReadableDatabase();
+    method public abstract android.arch.persistence.db.SupportSQLiteDatabase getWritableDatabase();
+    method public abstract void setWriteAheadLoggingEnabled(boolean);
+  }
+
+  public static abstract class SupportSQLiteOpenHelper.Callback {
+    ctor public SupportSQLiteOpenHelper.Callback(int);
+    method public void onConfigure(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void onCorruption(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public abstract void onCreate(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void onDowngrade(android.arch.persistence.db.SupportSQLiteDatabase, int, int);
+    method public void onOpen(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public abstract void onUpgrade(android.arch.persistence.db.SupportSQLiteDatabase, int, int);
+    field public final int version;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration {
+    method public static android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+    field public final android.arch.persistence.db.SupportSQLiteOpenHelper.Callback callback;
+    field public final android.content.Context context;
+    field public final java.lang.String name;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration.Builder {
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration build();
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder callback(android.arch.persistence.db.SupportSQLiteOpenHelper.Callback);
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder name(java.lang.String);
+  }
+
+  public static abstract interface SupportSQLiteOpenHelper.Factory {
+    method public abstract android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+  public abstract interface SupportSQLiteProgram implements java.io.Closeable {
+    method public abstract void bindBlob(int, byte[]);
+    method public abstract void bindDouble(int, double);
+    method public abstract void bindLong(int, long);
+    method public abstract void bindNull(int);
+    method public abstract void bindString(int, java.lang.String);
+    method public abstract void clearBindings();
+  }
+
+  public abstract interface SupportSQLiteQuery {
+    method public abstract void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+    method public abstract int getArgCount();
+    method public abstract java.lang.String getSql();
+  }
+
+  public final class SupportSQLiteQueryBuilder {
+    method public static android.arch.persistence.db.SupportSQLiteQueryBuilder builder(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder columns(java.lang.String[]);
+    method public android.arch.persistence.db.SupportSQLiteQuery create();
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder distinct();
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder groupBy(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder having(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder limit(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder orderBy(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder selection(java.lang.String, java.lang.Object[]);
+  }
+
+  public abstract interface SupportSQLiteStatement implements android.arch.persistence.db.SupportSQLiteProgram {
+    method public abstract void execute();
+    method public abstract long executeInsert();
+    method public abstract int executeUpdateDelete();
+    method public abstract long simpleQueryForLong();
+    method public abstract java.lang.String simpleQueryForString();
+  }
+
+}
+
diff --git a/persistence/db/api_legacy/current.txt b/persistence/db/api_legacy/current.txt
new file mode 100644
index 0000000..e0abe20
--- /dev/null
+++ b/persistence/db/api_legacy/current.txt
@@ -0,0 +1,125 @@
+package android.arch.persistence.db {
+
+  public final class SimpleSQLiteQuery implements android.arch.persistence.db.SupportSQLiteQuery {
+    ctor public SimpleSQLiteQuery(java.lang.String, java.lang.Object[]);
+    ctor public SimpleSQLiteQuery(java.lang.String);
+    method public static void bind(android.arch.persistence.db.SupportSQLiteProgram, java.lang.Object[]);
+    method public void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+    method public int getArgCount();
+    method public java.lang.String getSql();
+  }
+
+  public abstract interface SupportSQLiteDatabase implements java.io.Closeable {
+    method public abstract void beginTransaction();
+    method public abstract void beginTransactionNonExclusive();
+    method public abstract void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener);
+    method public abstract void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
+    method public abstract android.arch.persistence.db.SupportSQLiteStatement compileStatement(java.lang.String);
+    method public abstract int delete(java.lang.String, java.lang.String, java.lang.Object[]);
+    method public abstract void disableWriteAheadLogging();
+    method public abstract boolean enableWriteAheadLogging();
+    method public abstract void endTransaction();
+    method public abstract void execSQL(java.lang.String) throws android.database.SQLException;
+    method public abstract void execSQL(java.lang.String, java.lang.Object[]) throws android.database.SQLException;
+    method public abstract java.util.List<android.util.Pair<java.lang.String, java.lang.String>> getAttachedDbs();
+    method public abstract long getMaximumSize();
+    method public abstract long getPageSize();
+    method public abstract java.lang.String getPath();
+    method public abstract int getVersion();
+    method public abstract boolean inTransaction();
+    method public abstract long insert(java.lang.String, int, android.content.ContentValues) throws android.database.SQLException;
+    method public abstract boolean isDatabaseIntegrityOk();
+    method public abstract boolean isDbLockedByCurrentThread();
+    method public abstract boolean isOpen();
+    method public abstract boolean isReadOnly();
+    method public abstract boolean isWriteAheadLoggingEnabled();
+    method public abstract boolean needUpgrade(int);
+    method public abstract android.database.Cursor query(java.lang.String);
+    method public abstract android.database.Cursor query(java.lang.String, java.lang.Object[]);
+    method public abstract android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery);
+    method public abstract android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery, android.os.CancellationSignal);
+    method public abstract void setForeignKeyConstraintsEnabled(boolean);
+    method public abstract void setLocale(java.util.Locale);
+    method public abstract void setMaxSqlCacheSize(int);
+    method public abstract long setMaximumSize(long);
+    method public abstract void setPageSize(long);
+    method public abstract void setTransactionSuccessful();
+    method public abstract void setVersion(int);
+    method public abstract int update(java.lang.String, int, android.content.ContentValues, java.lang.String, java.lang.Object[]);
+    method public abstract boolean yieldIfContendedSafely();
+    method public abstract boolean yieldIfContendedSafely(long);
+  }
+
+  public abstract interface SupportSQLiteOpenHelper {
+    method public abstract void close();
+    method public abstract java.lang.String getDatabaseName();
+    method public abstract android.arch.persistence.db.SupportSQLiteDatabase getReadableDatabase();
+    method public abstract android.arch.persistence.db.SupportSQLiteDatabase getWritableDatabase();
+    method public abstract void setWriteAheadLoggingEnabled(boolean);
+  }
+
+  public static abstract class SupportSQLiteOpenHelper.Callback {
+    ctor public SupportSQLiteOpenHelper.Callback(int);
+    method public void onConfigure(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void onCorruption(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public abstract void onCreate(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void onDowngrade(android.arch.persistence.db.SupportSQLiteDatabase, int, int);
+    method public void onOpen(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public abstract void onUpgrade(android.arch.persistence.db.SupportSQLiteDatabase, int, int);
+    field public final int version;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration {
+    method public static android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+    field public final android.arch.persistence.db.SupportSQLiteOpenHelper.Callback callback;
+    field public final android.content.Context context;
+    field public final java.lang.String name;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration.Builder {
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration build();
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder callback(android.arch.persistence.db.SupportSQLiteOpenHelper.Callback);
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration.Builder name(java.lang.String);
+  }
+
+  public static abstract interface SupportSQLiteOpenHelper.Factory {
+    method public abstract android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+  public abstract interface SupportSQLiteProgram implements java.io.Closeable {
+    method public abstract void bindBlob(int, byte[]);
+    method public abstract void bindDouble(int, double);
+    method public abstract void bindLong(int, long);
+    method public abstract void bindNull(int);
+    method public abstract void bindString(int, java.lang.String);
+    method public abstract void clearBindings();
+  }
+
+  public abstract interface SupportSQLiteQuery {
+    method public abstract void bindTo(android.arch.persistence.db.SupportSQLiteProgram);
+    method public abstract int getArgCount();
+    method public abstract java.lang.String getSql();
+  }
+
+  public final class SupportSQLiteQueryBuilder {
+    method public static android.arch.persistence.db.SupportSQLiteQueryBuilder builder(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder columns(java.lang.String[]);
+    method public android.arch.persistence.db.SupportSQLiteQuery create();
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder distinct();
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder groupBy(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder having(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder limit(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder orderBy(java.lang.String);
+    method public android.arch.persistence.db.SupportSQLiteQueryBuilder selection(java.lang.String, java.lang.Object[]);
+  }
+
+  public abstract interface SupportSQLiteStatement implements android.arch.persistence.db.SupportSQLiteProgram {
+    method public abstract void execute();
+    method public abstract long executeInsert();
+    method public abstract int executeUpdateDelete();
+    method public abstract long simpleQueryForLong();
+    method public abstract java.lang.String simpleQueryForString();
+  }
+
+}
+
diff --git a/persistence/db/src/main/AndroidManifest.xml b/persistence/db/src/main/AndroidManifest.xml
index 8a27324..8b4263b 100644
--- a/persistence/db/src/main/AndroidManifest.xml
+++ b/persistence/db/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.persistence.db">
+          package="androidx.sqlite.db">
 </manifest>
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java b/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
deleted file mode 100644
index 13faf24..0000000
--- a/persistence/db/src/main/java/android/arch/persistence/db/SimpleSQLiteQuery.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db;
-
-/**
- * A basic implementation of {@link SupportSQLiteQuery} which receives a query and its args and
- * binds args based on the passed in Object type.
- */
-public final class SimpleSQLiteQuery implements SupportSQLiteQuery {
-    private final String mQuery;
-    private final Object[] mBindArgs;
-
-    /**
-     * Creates an SQL query with the sql string and the bind arguments.
-     *
-     * @param query    The query string, can include bind arguments (.e.g ?).
-     * @param bindArgs The bind argument value that will replace the placeholders in the query.
-     */
-    public SimpleSQLiteQuery(String query, Object[] bindArgs) {
-        mQuery = query;
-        mBindArgs = bindArgs;
-    }
-
-    /**
-     * Creates an SQL query without any bind arguments.
-     *
-     * @param query The SQL query to execute. Cannot include bind parameters.
-     */
-    public SimpleSQLiteQuery(String query) {
-        this(query, null);
-    }
-
-    @Override
-    public String getSql() {
-        return mQuery;
-    }
-
-    @Override
-    public void bindTo(SupportSQLiteProgram statement) {
-        bind(statement, mBindArgs);
-    }
-
-    @Override
-    public int getArgCount() {
-        return mBindArgs.length;
-    }
-
-    /**
-     * Binds the given arguments into the given sqlite statement.
-     *
-     * @param statement The sqlite statement
-     * @param bindArgs  The list of bind arguments
-     */
-    public static void bind(SupportSQLiteProgram statement, Object[] bindArgs) {
-        if (bindArgs == null) {
-            return;
-        }
-        final int limit = bindArgs.length;
-        for (int i = 0; i < limit; i++) {
-            final Object arg = bindArgs[i];
-            bind(statement, i + 1, arg);
-        }
-    }
-
-    private static void bind(SupportSQLiteProgram statement, int index, Object arg) {
-        // extracted from android.database.sqlite.SQLiteConnection
-        if (arg == null) {
-            statement.bindNull(index);
-        } else if (arg instanceof byte[]) {
-            statement.bindBlob(index, (byte[]) arg);
-        } else if (arg instanceof Float) {
-            statement.bindDouble(index, (Float) arg);
-        } else if (arg instanceof Double) {
-            statement.bindDouble(index, (Double) arg);
-        } else if (arg instanceof Long) {
-            statement.bindLong(index, (Long) arg);
-        } else if (arg instanceof Integer) {
-            statement.bindLong(index, (Integer) arg);
-        } else if (arg instanceof Short) {
-            statement.bindLong(index, (Short) arg);
-        } else if (arg instanceof Byte) {
-            statement.bindLong(index, (Byte) arg);
-        } else if (arg instanceof String) {
-            statement.bindString(index, (String) arg);
-        } else if (arg instanceof Boolean) {
-            statement.bindLong(index, ((Boolean) arg) ? 1 : 0);
-        } else {
-            throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index
-                    + " Supported types: null, byte[], float, double, long, int, short, byte,"
-                    + " string");
-        }
-    }
-}
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java
deleted file mode 100644
index 5f71baf..0000000
--- a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteDatabase.java
+++ /dev/null
@@ -1,605 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteTransactionListener;
-import android.os.Build;
-import android.os.CancellationSignal;
-import android.os.OperationCanceledException;
-import android.support.annotation.RequiresApi;
-import android.util.Pair;
-
-import java.io.Closeable;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * A database abstraction which removes the framework dependency and allows swapping underlying
- * sql versions. It mimics the behavior of {@link android.database.sqlite.SQLiteDatabase}
- */
-@SuppressWarnings("unused")
-public interface SupportSQLiteDatabase extends Closeable {
-    /**
-     * Compiles the given SQL statement.
-     *
-     * @param sql The sql query.
-     * @return Compiled statement.
-     */
-    SupportSQLiteStatement compileStatement(String sql);
-
-    /**
-     * Begins a transaction in EXCLUSIVE mode.
-     * <p>
-     * Transactions can be nested.
-     * When the outer transaction is ended all of
-     * the work done in that transaction and all of the nested transactions will be committed or
-     * rolled back. The changes will be rolled back if any transaction is ended without being
-     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
-     * </p>
-     * <p>Here is the standard idiom for transactions:
-     *
-     * <pre>
-     *   db.beginTransaction();
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * </pre>
-     */
-    void beginTransaction();
-
-    /**
-     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
-     * the outer transaction is ended all of the work done in that transaction
-     * and all of the nested transactions will be committed or rolled back. The
-     * changes will be rolled back if any transaction is ended without being
-     * marked as clean (by calling setTransactionSuccessful). Otherwise they
-     * will be committed.
-     * <p>
-     * Here is the standard idiom for transactions:
-     *
-     * <pre>
-     *   db.beginTransactionNonExclusive();
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * </pre>
-     */
-    void beginTransactionNonExclusive();
-
-    /**
-     * Begins a transaction in EXCLUSIVE mode.
-     * <p>
-     * Transactions can be nested.
-     * When the outer transaction is ended all of
-     * the work done in that transaction and all of the nested transactions will be committed or
-     * rolled back. The changes will be rolled back if any transaction is ended without being
-     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
-     * </p>
-     * <p>Here is the standard idiom for transactions:
-     *
-     * <pre>
-     *   db.beginTransactionWithListener(listener);
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * </pre>
-     *
-     * @param transactionListener listener that should be notified when the transaction begins,
-     *                            commits, or is rolled back, either explicitly or by a call to
-     *                            {@link #yieldIfContendedSafely}.
-     */
-    void beginTransactionWithListener(SQLiteTransactionListener transactionListener);
-
-    /**
-     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
-     * the outer transaction is ended all of the work done in that transaction
-     * and all of the nested transactions will be committed or rolled back. The
-     * changes will be rolled back if any transaction is ended without being
-     * marked as clean (by calling setTransactionSuccessful). Otherwise they
-     * will be committed.
-     * <p>
-     * Here is the standard idiom for transactions:
-     *
-     * <pre>
-     *   db.beginTransactionWithListenerNonExclusive(listener);
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * </pre>
-     *
-     * @param transactionListener listener that should be notified when the
-     *                            transaction begins, commits, or is rolled back, either
-     *                            explicitly or by a call to {@link #yieldIfContendedSafely}.
-     */
-    void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener);
-
-    /**
-     * End a transaction. See beginTransaction for notes about how to use this and when transactions
-     * are committed and rolled back.
-     */
-    void endTransaction();
-
-    /**
-     * Marks the current transaction as successful. Do not do any more database work between
-     * calling this and calling endTransaction. Do as little non-database work as possible in that
-     * situation too. If any errors are encountered between this and endTransaction the transaction
-     * will still be committed.
-     *
-     * @throws IllegalStateException if the current thread is not in a transaction or the
-     *                               transaction is already marked as successful.
-     */
-    void setTransactionSuccessful();
-
-    /**
-     * Returns true if the current thread has a transaction pending.
-     *
-     * @return True if the current thread is in a transaction.
-     */
-    boolean inTransaction();
-
-    /**
-     * Returns true if the current thread is holding an active connection to the database.
-     * <p>
-     * The name of this method comes from a time when having an active connection
-     * to the database meant that the thread was holding an actual lock on the
-     * database.  Nowadays, there is no longer a true "database lock" although threads
-     * may block if they cannot acquire a database connection to perform a
-     * particular operation.
-     * </p>
-     *
-     * @return True if the current thread is holding an active connection to the database.
-     */
-    boolean isDbLockedByCurrentThread();
-
-    /**
-     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
-     * successful so far. Do not call setTransactionSuccessful before calling this. When this
-     * returns a new transaction will have been created but not marked as successful. This assumes
-     * that there are no nested transactions (beginTransaction has only been called once) and will
-     * throw an exception if that is not the case.
-     *
-     * @return true if the transaction was yielded
-     */
-    boolean yieldIfContendedSafely();
-
-    /**
-     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
-     * successful so far. Do not call setTransactionSuccessful before calling this. When this
-     * returns a new transaction will have been created but not marked as successful. This assumes
-     * that there are no nested transactions (beginTransaction has only been called once) and will
-     * throw an exception if that is not the case.
-     *
-     * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if
-     *                             the lock was actually yielded. This will allow other background
-     *                             threads to make some
-     *                             more progress than they would if we started the transaction
-     *                             immediately.
-     * @return true if the transaction was yielded
-     */
-    boolean yieldIfContendedSafely(long sleepAfterYieldDelay);
-
-    /**
-     * Gets the database version.
-     *
-     * @return the database version
-     */
-    int getVersion();
-
-    /**
-     * Sets the database version.
-     *
-     * @param version the new database version
-     */
-    void setVersion(int version);
-
-    /**
-     * Returns the maximum size the database may grow to.
-     *
-     * @return the new maximum database size
-     */
-    long getMaximumSize();
-
-    /**
-     * Sets the maximum size the database will grow to. The maximum size cannot
-     * be set below the current size.
-     *
-     * @param numBytes the maximum database size, in bytes
-     * @return the new maximum database size
-     */
-    long setMaximumSize(long numBytes);
-
-    /**
-     * Returns the current database page size, in bytes.
-     *
-     * @return the database page size, in bytes
-     */
-    long getPageSize();
-
-    /**
-     * Sets the database page size. The page size must be a power of two. This
-     * method does not work if any data has been written to the database file,
-     * and must be called right after the database has been created.
-     *
-     * @param numBytes the database page size, in bytes
-     */
-    void setPageSize(long numBytes);
-
-    /**
-     * Runs the given query on the database. If you would like to have typed bind arguments,
-     * use {@link #query(SupportSQLiteQuery)}.
-     *
-     * @param query The SQL query that includes the query and can bind into a given compiled
-     *              program.
-     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
-     * {@link Cursor}s are not synchronized, see the documentation for more details.
-     * @see #query(SupportSQLiteQuery)
-     */
-    Cursor query(String query);
-
-    /**
-     * Runs the given query on the database. If you would like to have bind arguments,
-     * use {@link #query(SupportSQLiteQuery)}.
-     *
-     * @param query The SQL query that includes the query and can bind into a given compiled
-     *              program.
-     * @param bindArgs The query arguments to bind.
-     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
-     * {@link Cursor}s are not synchronized, see the documentation for more details.
-     * @see #query(SupportSQLiteQuery)
-     */
-    Cursor query(String query, Object[] bindArgs);
-
-    /**
-     * Runs the given query on the database.
-     * <p>
-     * This class allows using type safe sql program bindings while running queries.
-     *
-     * @param query The SQL query that includes the query and can bind into a given compiled
-     *              program.
-     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
-     * {@link Cursor}s are not synchronized, see the documentation for more details.
-     * @see SimpleSQLiteQuery
-     */
-    Cursor query(SupportSQLiteQuery query);
-
-    /**
-     * Runs the given query on the database.
-     * <p>
-     * This class allows using type safe sql program bindings while running queries.
-     *
-     * @param query The SQL query that includes the query and can bind into a given compiled
-     *              program.
-     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
-     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
-     * when the query is executed.
-     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
-     * {@link Cursor}s are not synchronized, see the documentation for more details.
-     */
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal);
-
-    /**
-     * Convenience method for inserting a row into the database.
-     *
-     * @param table          the table to insert the row into
-     * @param values         this map contains the initial column values for the
-     *                       row. The keys should be the column names and the values the
-     *                       column values
-     * @param conflictAlgorithm for insert conflict resolver. One of
-     * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK},
-     * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL},
-     * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}.
-     * @return the row ID of the newly inserted row, or -1 if an error occurred
-     * @throws SQLException If the insert fails
-     */
-    long insert(String table, int conflictAlgorithm, ContentValues values) throws SQLException;
-
-    /**
-     * Convenience method for deleting rows in the database.
-     *
-     * @param table       the table to delete from
-     * @param whereClause the optional WHERE clause to apply when deleting.
-     *                    Passing null will delete all rows.
-     * @param whereArgs   You may include ?s in the where clause, which
-     *                    will be replaced by the values from whereArgs. The values
-     *                    will be bound as Strings.
-     * @return the number of rows affected if a whereClause is passed in, 0
-     * otherwise. To remove all rows and get a count pass "1" as the
-     * whereClause.
-     */
-    int delete(String table, String whereClause, Object[] whereArgs);
-
-    /**
-     * Convenience method for updating rows in the database.
-     *
-     * @param table       the table to update in
-     * @param conflictAlgorithm for update conflict resolver. One of
-     * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK},
-     * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL},
-     * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}.
-     * @param values      a map from column names to new column values. null is a
-     *                    valid value that will be translated to NULL.
-     * @param whereClause the optional WHERE clause to apply when updating.
-     *                    Passing null will update all rows.
-     * @param whereArgs   You may include ?s in the where clause, which
-     *                    will be replaced by the values from whereArgs. The values
-     *                    will be bound as Strings.
-     * @return the number of rows affected
-     */
-    int update(String table, int conflictAlgorithm,
-            ContentValues values, String whereClause, Object[] whereArgs);
-
-    /**
-     * Execute a single SQL statement that does not return any data.
-     * <p>
-     * When using {@link #enableWriteAheadLogging()}, journal_mode is
-     * automatically managed by this class. So, do not set journal_mode
-     * using "PRAGMA journal_mode'<value>" statement if your app is using
-     * {@link #enableWriteAheadLogging()}
-     * </p>
-     *
-     * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are
-     *            not supported.
-     * @throws SQLException if the SQL string is invalid
-     * @see #query(SupportSQLiteQuery)
-     */
-    void execSQL(String sql) throws SQLException;
-
-    /**
-     * Execute a single SQL statement that does not return any data.
-     * <p>
-     * When using {@link #enableWriteAheadLogging()}, journal_mode is
-     * automatically managed by this class. So, do not set journal_mode
-     * using "PRAGMA journal_mode'<value>" statement if your app is using
-     * {@link #enableWriteAheadLogging()}
-     * </p>
-     *
-     * @param sql      the SQL statement to be executed. Multiple statements separated by semicolons
-     *                 are
-     *                 not supported.
-     * @param bindArgs only byte[], String, Long and Double are supported in selectionArgs.
-     * @throws SQLException if the SQL string is invalid
-     * @see #query(SupportSQLiteQuery)
-     */
-    void execSQL(String sql, Object[] bindArgs) throws SQLException;
-
-    /**
-     * Returns true if the database is opened as read only.
-     *
-     * @return True if database is opened as read only.
-     */
-    boolean isReadOnly();
-
-    /**
-     * Returns true if the database is currently open.
-     *
-     * @return True if the database is currently open (has not been closed).
-     */
-    boolean isOpen();
-
-    /**
-     * Returns true if the new version code is greater than the current database version.
-     *
-     * @param newVersion The new version code.
-     * @return True if the new version code is greater than the current database version.
-     */
-    boolean needUpgrade(int newVersion);
-
-    /**
-     * Gets the path to the database file.
-     *
-     * @return The path to the database file.
-     */
-    String getPath();
-
-    /**
-     * Sets the locale for this database.  Does nothing if this database has
-     * the {@link SQLiteDatabase#NO_LOCALIZED_COLLATORS} flag set or was opened read only.
-     *
-     * @param locale The new locale.
-     * @throws SQLException if the locale could not be set.  The most common reason
-     *                      for this is that there is no collator available for the locale you
-     *                      requested.
-     *                      In this case the database remains unchanged.
-     */
-    void setLocale(Locale locale);
-
-    /**
-     * Sets the maximum size of the prepared-statement cache for this database.
-     * (size of the cache = number of compiled-sql-statements stored in the cache).
-     * <p>
-     * Maximum cache size can ONLY be increased from its current size (default = 10).
-     * If this method is called with smaller size than the current maximum value,
-     * then IllegalStateException is thrown.
-     * <p>
-     * This method is thread-safe.
-     *
-     * @param cacheSize the size of the cache. can be (0 to
-     *                  {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE})
-     * @throws IllegalStateException if input cacheSize gt;
-     *                               {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE}.
-     */
-    void setMaxSqlCacheSize(int cacheSize);
-
-    /**
-     * Sets whether foreign key constraints are enabled for the database.
-     * <p>
-     * By default, foreign key constraints are not enforced by the database.
-     * This method allows an application to enable foreign key constraints.
-     * It must be called each time the database is opened to ensure that foreign
-     * key constraints are enabled for the session.
-     * </p><p>
-     * A good time to call this method is right after calling {@code #openOrCreateDatabase}
-     * or in the {@link SupportSQLiteOpenHelper.Callback#onConfigure} callback.
-     * </p><p>
-     * When foreign key constraints are disabled, the database does not check whether
-     * changes to the database will violate foreign key constraints.  Likewise, when
-     * foreign key constraints are disabled, the database will not execute cascade
-     * delete or update triggers.  As a result, it is possible for the database
-     * state to become inconsistent.  To perform a database integrity check,
-     * call {@link #isDatabaseIntegrityOk}.
-     * </p><p>
-     * This method must not be called while a transaction is in progress.
-     * </p><p>
-     * See also <a href="http://sqlite.org/foreignkeys.html">SQLite Foreign Key Constraints</a>
-     * for more details about foreign key constraint support.
-     * </p>
-     *
-     * @param enable True to enable foreign key constraints, false to disable them.
-     * @throws IllegalStateException if the are transactions is in progress
-     *                               when this method is called.
-     */
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    void setForeignKeyConstraintsEnabled(boolean enable);
-
-    /**
-     * This method enables parallel execution of queries from multiple threads on the
-     * same database.  It does this by opening multiple connections to the database
-     * and using a different database connection for each query.  The database
-     * journal mode is also changed to enable writes to proceed concurrently with reads.
-     * <p>
-     * When write-ahead logging is not enabled (the default), it is not possible for
-     * reads and writes to occur on the database at the same time.  Before modifying the
-     * database, the writer implicitly acquires an exclusive lock on the database which
-     * prevents readers from accessing the database until the write is completed.
-     * </p><p>
-     * In contrast, when write-ahead logging is enabled (by calling this method), write
-     * operations occur in a separate log file which allows reads to proceed concurrently.
-     * While a write is in progress, readers on other threads will perceive the state
-     * of the database as it was before the write began.  When the write completes, readers
-     * on other threads will then perceive the new state of the database.
-     * </p><p>
-     * It is a good idea to enable write-ahead logging whenever a database will be
-     * concurrently accessed and modified by multiple threads at the same time.
-     * However, write-ahead logging uses significantly more memory than ordinary
-     * journaling because there are multiple connections to the same database.
-     * So if a database will only be used by a single thread, or if optimizing
-     * concurrency is not very important, then write-ahead logging should be disabled.
-     * </p><p>
-     * After calling this method, execution of queries in parallel is enabled as long as
-     * the database remains open.  To disable execution of queries in parallel, either
-     * call {@link #disableWriteAheadLogging} or close the database and reopen it.
-     * </p><p>
-     * The maximum number of connections used to execute queries in parallel is
-     * dependent upon the device memory and possibly other properties.
-     * </p><p>
-     * If a query is part of a transaction, then it is executed on the same database handle the
-     * transaction was begun.
-     * </p><p>
-     * Writers should use {@link #beginTransactionNonExclusive()} or
-     * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)}
-     * to start a transaction.  Non-exclusive mode allows database file to be in readable
-     * by other threads executing queries.
-     * </p><p>
-     * If the database has any attached databases, then execution of queries in parallel is NOT
-     * possible.  Likewise, write-ahead logging is not supported for read-only databases
-     * or memory databases.  In such cases, {@code enableWriteAheadLogging} returns false.
-     * </p><p>
-     * The best way to enable write-ahead logging is to pass the
-     * {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag to
-     * {@link SQLiteDatabase#openDatabase}.  This is more efficient than calling
-     * <code><pre>
-     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
-     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
-     *             myDatabaseErrorHandler);
-     *     db.enableWriteAheadLogging();
-     * </pre></code>
-     * </p><p>
-     * Another way to enable write-ahead logging is to call {@code enableWriteAheadLogging}
-     * after opening the database.
-     * <code><pre>
-     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
-     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
-     *     db.enableWriteAheadLogging();
-     * </pre></code>
-     * </p><p>
-     * See also <a href="http://sqlite.org/wal.html">SQLite Write-Ahead Logging</a> for
-     * more details about how write-ahead logging works.
-     * </p>
-     *
-     * @return True if write-ahead logging is enabled.
-     * @throws IllegalStateException if there are transactions in progress at the
-     *                               time this method is called.  WAL mode can only be changed when
-     *                               there are no
-     *                               transactions in progress.
-     * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING
-     * @see #disableWriteAheadLogging
-     */
-    boolean enableWriteAheadLogging();
-
-    /**
-     * This method disables the features enabled by {@link #enableWriteAheadLogging()}.
-     *
-     * @throws IllegalStateException if there are transactions in progress at the
-     *                               time this method is called.  WAL mode can only be changed when
-     *                               there are no
-     *                               transactions in progress.
-     * @see #enableWriteAheadLogging
-     */
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    void disableWriteAheadLogging();
-
-    /**
-     * Returns true if write-ahead logging has been enabled for this database.
-     *
-     * @return True if write-ahead logging has been enabled for this database.
-     * @see #enableWriteAheadLogging
-     * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING
-     */
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    boolean isWriteAheadLoggingEnabled();
-
-    /**
-     * Returns list of full path names of all attached databases including the main database
-     * by executing 'pragma database_list' on the database.
-     *
-     * @return ArrayList of pairs of (database name, database file path) or null if the database
-     * is not open.
-     */
-    List<Pair<String, String>> getAttachedDbs();
-
-    /**
-     * Runs 'pragma integrity_check' on the given database (and all the attached databases)
-     * and returns true if the given database (and all its attached databases) pass integrity_check,
-     * false otherwise.
-     * <p>
-     * If the result is false, then this method logs the errors reported by the integrity_check
-     * command execution.
-     * <p>
-     * Note that 'pragma integrity_check' on a database can take a long time.
-     *
-     * @return true if the given database (and all its attached databases) pass integrity_check,
-     * false otherwise.
-     */
-    boolean isDatabaseIntegrityOk();
-}
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java
deleted file mode 100644
index 02e4e7d..0000000
--- a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteOpenHelper.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db;
-
-import android.content.Context;
-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}.
- * Note that since that class requires overriding certain methods, support implementation
- * uses {@link Factory#create(Configuration)} to create this and {@link Callback} to implement
- * the methods that should be overridden.
- */
-@SuppressWarnings("unused")
-public interface SupportSQLiteOpenHelper {
-    /**
-     * Return the name of the SQLite database being opened, as given to
-     * the constructor.
-     */
-    String getDatabaseName();
-
-    /**
-     * Enables or disables the use of write-ahead logging for the database.
-     *
-     * Write-ahead logging cannot be used with read-only databases so the value of
-     * this flag is ignored if the database is opened read-only.
-     *
-     * @param enabled True if write-ahead logging should be enabled, false if it
-     *                should be disabled.
-     * @see SupportSQLiteDatabase#enableWriteAheadLogging()
-     */
-    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
-    void setWriteAheadLoggingEnabled(boolean enabled);
-
-    /**
-     * Create and/or open a database that will be used for reading and writing.
-     * The first time this is called, the database will be opened and
-     * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be
-     * called.
-     *
-     * <p>Once opened successfully, the database is cached, so you can
-     * call this method every time you need to write to the database.
-     * (Make sure to call {@link #close} when you no longer need the database.)
-     * Errors such as bad permissions or a full disk may cause this method
-     * to fail, but future attempts may succeed if the problem is fixed.</p>
-     *
-     * <p class="caution">Database upgrade may take a long time, you
-     * should not call this method from the application main thread, including
-     * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
-     *
-     * @return a read/write database object valid until {@link #close} is called
-     * @throws SQLiteException if the database cannot be opened for writing
-     */
-    SupportSQLiteDatabase getWritableDatabase();
-
-    /**
-     * Create and/or open a database.  This will be the same object returned by
-     * {@link #getWritableDatabase} unless some problem, such as a full disk,
-     * requires the database to be opened read-only.  In that case, a read-only
-     * database object will be returned.  If the problem is fixed, a future call
-     * to {@link #getWritableDatabase} may succeed, in which case the read-only
-     * database object will be closed and the read/write object will be returned
-     * in the future.
-     *
-     * <p class="caution">Like {@link #getWritableDatabase}, this method may
-     * take a long time to return, so you should not call it from the
-     * application main thread, including from
-     * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
-     *
-     * @return a database object valid until {@link #getWritableDatabase}
-     * or {@link #close} is called.
-     * @throws SQLiteException if the database cannot be opened
-     */
-    SupportSQLiteDatabase getReadableDatabase();
-
-    /**
-     * Close any open database object.
-     */
-    void close();
-
-    /**
-     * 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.
-         * <p>
-         * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade},
-         * or {@link #onOpen} are called. It should not modify the database except to configure the
-         * database connection as required.
-         * </p>
-         * <p>
-         * This method should only call methods that configure the parameters of the database
-         * connection, such as {@link SupportSQLiteDatabase#enableWriteAheadLogging}
-         * {@link SupportSQLiteDatabase#setForeignKeyConstraintsEnabled},
-         * {@link SupportSQLiteDatabase#setLocale},
-         * {@link SupportSQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
-         * </p>
-         *
-         * @param db The database.
-         */
-        public void onConfigure(SupportSQLiteDatabase db) {
-
-        }
-
-        /**
-         * Called when the database is created for the first time. This is where the
-         * creation of tables and the initial population of the tables should happen.
-         *
-         * @param db The database.
-         */
-        public abstract void onCreate(SupportSQLiteDatabase db);
-
-        /**
-         * Called when the database needs to be upgraded. The implementation
-         * should use this method to drop tables, add tables, or do anything else it
-         * needs to upgrade to the new schema version.
-         *
-         * <p>
-         * The SQLite ALTER TABLE documentation can be found
-         * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
-         * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
-         * you can use ALTER TABLE to rename the old table, then create the new table and then
-         * populate the new table with the contents of the old table.
-         * </p><p>
-         * This method executes within a transaction.  If an exception is thrown, all changes
-         * will automatically be rolled back.
-         * </p>
-         *
-         * @param db         The database.
-         * @param oldVersion The old database version.
-         * @param newVersion The new database version.
-         */
-        public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);
-
-        /**
-         * Called when the database needs to be downgraded. This is strictly similar to
-         * {@link #onUpgrade} method, but is called whenever current version is newer than requested
-         * one.
-         * However, this method is not abstract, so it is not mandatory for a customer to
-         * implement it. If not overridden, default implementation will reject downgrade and
-         * throws SQLiteException
-         *
-         * <p>
-         * This method executes within a transaction.  If an exception is thrown, all changes
-         * will automatically be rolled back.
-         * </p>
-         *
-         * @param db         The database.
-         * @param oldVersion The old database version.
-         * @param newVersion The new database version.
-         */
-        public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
-            throw new SQLiteException("Can't downgrade database from version "
-                    + oldVersion + " to " + newVersion);
-        }
-
-        /**
-         * Called when the database has been opened.  The implementation
-         * should check {@link SupportSQLiteDatabase#isReadOnly} before updating the
-         * database.
-         * <p>
-         * This method is called after the database connection has been configured
-         * and after the database schema has been created, upgraded or downgraded as necessary.
-         * If the database connection must be configured in some way before the schema
-         * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
-         * </p>
-         *
-         * @param db The database.
-         */
-        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);
-            }
-        }
-    }
-
-    /**
-     * The configuration to create an SQLite open helper object using {@link Factory}.
-     */
-    @SuppressWarnings("WeakerAccess")
-    class Configuration {
-        /**
-         * Context to use to open or create the database.
-         */
-        @NonNull
-        public final Context context;
-        /**
-         * Name of the database file, or null for an in-memory database.
-         */
-        @Nullable
-        public final String name;
-        /**
-         * The callback class to handle creation, upgrade and downgrade.
-         */
-        @NonNull
-        public final SupportSQLiteOpenHelper.Callback callback;
-
-        Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
-            this.context = context;
-            this.name = name;
-            this.callback = callback;
-        }
-
-        /**
-         * Creates a new Configuration.Builder to create an instance of Configuration.
-         *
-         * @param context to use to open or create the database.
-         */
-        public static Builder builder(Context context) {
-            return new Builder(context);
-        }
-
-        /**
-         * Builder class for {@link Configuration}.
-         */
-        public static class Builder {
-            Context mContext;
-            String mName;
-            SupportSQLiteOpenHelper.Callback mCallback;
-
-            public Configuration build() {
-                if (mCallback == null) {
-                    throw new IllegalArgumentException("Must set a callback to create the"
-                            + " configuration.");
-                }
-                if (mContext == null) {
-                    throw new IllegalArgumentException("Must set a non-null context to create"
-                            + " the configuration.");
-                }
-                return new Configuration(mContext, mName, mCallback);
-            }
-
-            Builder(@NonNull Context context) {
-                mContext = context;
-            }
-
-            /**
-             * @param name Name of the database file, or null for an in-memory database.
-             * @return This
-             */
-            public Builder name(@Nullable String name) {
-                mName = name;
-                return this;
-            }
-
-            /**
-             * @param callback The callback class to handle creation, upgrade and downgrade.
-             * @return this
-             */
-            public Builder callback(@NonNull Callback callback) {
-                mCallback = callback;
-                return this;
-            }
-        }
-    }
-
-    /**
-     * Factory class to create instances of {@link SupportSQLiteOpenHelper} using
-     * {@link Configuration}.
-     */
-    interface Factory {
-        /**
-         * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration.
-         *
-         * @param configuration The configuration to use while creating the open helper.
-         *
-         * @return A SupportSQLiteOpenHelper which can be used to open a database.
-         */
-        SupportSQLiteOpenHelper create(Configuration configuration);
-    }
-}
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java
deleted file mode 100644
index 38c1ac1..0000000
--- a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteProgram.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db;
-
-import java.io.Closeable;
-
-/**
- * An interface to map the behavior of {@link android.database.sqlite.SQLiteProgram}.
- */
-
-@SuppressWarnings("unused")
-public interface SupportSQLiteProgram extends Closeable {
-    /**
-     * Bind a NULL value to this statement. The value remains bound until
-     * {@link #clearBindings} is called.
-     *
-     * @param index The 1-based index to the parameter to bind null to
-     */
-    void bindNull(int index);
-
-    /**
-     * Bind a long value to this statement. The value remains bound until
-     * {@link #clearBindings} is called.
-     *addToBindArgs
-     * @param index The 1-based index to the parameter to bind
-     * @param value The value to bind
-     */
-    void bindLong(int index, long value);
-
-    /**
-     * Bind a double value to this statement. The value remains bound until
-     * {@link #clearBindings} is called.
-     *
-     * @param index The 1-based index to the parameter to bind
-     * @param value The value to bind
-     */
-    void bindDouble(int index, double value);
-
-    /**
-     * Bind a String value to this statement. The value remains bound until
-     * {@link #clearBindings} is called.
-     *
-     * @param index The 1-based index to the parameter to bind
-     * @param value The value to bind, must not be null
-     */
-    void bindString(int index, String value);
-
-    /**
-     * Bind a byte array value to this statement. The value remains bound until
-     * {@link #clearBindings} is called.
-     *
-     * @param index The 1-based index to the parameter to bind
-     * @param value The value to bind, must not be null
-     */
-    void bindBlob(int index, byte[] value);
-
-    /**
-     * Clears all existing bindings. Unset bindings are treated as NULL.
-     */
-    void clearBindings();
-}
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java
deleted file mode 100644
index 03e0a91..0000000
--- a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQuery.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db;
-
-/**
- * A query with typed bindings. It is better to use this API instead of
- * {@link android.database.sqlite.SQLiteDatabase#rawQuery(String, String[])} because it allows
- * binding type safe parameters.
- */
-public interface SupportSQLiteQuery {
-    /**
-     * The SQL query. This query can have placeholders(?) for bind arguments.
-     *
-     * @return The SQL query to compile
-     */
-    String getSql();
-
-    /**
-     * Callback to bind the query parameters to the compiled statement.
-     *
-     * @param statement The compiled statement
-     */
-    void bindTo(SupportSQLiteProgram statement);
-
-    /**
-     * Returns the number of arguments in this query. This is equal to the number of placeholders
-     * in the query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details.
-     *
-     * @return The number of arguments in the query.
-     */
-    int getArgCount();
-}
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
deleted file mode 100644
index 52957a3..0000000
--- a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db;
-
-import java.util.regex.Pattern;
-
-/**
- * A simple query builder to create SQL SELECT queries.
- */
-@SuppressWarnings("unused")
-public final class SupportSQLiteQueryBuilder {
-    private static final Pattern sLimitPattern =
-            Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
-
-    private boolean mDistinct = false;
-    private final String mTable;
-    private String[] mColumns = null;
-    private String mSelection;
-    private Object[] mBindArgs;
-    private String mGroupBy = null;
-    private String mHaving = null;
-    private String mOrderBy = null;
-    private String mLimit = null;
-
-    /**
-     * Creates a query for the given table name.
-     *
-     * @param tableName The table name(s) to query.
-     *
-     * @return A builder to create a query.
-     */
-    public static SupportSQLiteQueryBuilder builder(String tableName) {
-        return new SupportSQLiteQueryBuilder(tableName);
-    }
-
-    private SupportSQLiteQueryBuilder(String table) {
-        mTable = table;
-    }
-
-    /**
-     * Adds DISTINCT keyword to the query.
-     *
-     * @return this
-     */
-    public SupportSQLiteQueryBuilder distinct() {
-        mDistinct = true;
-        return this;
-    }
-
-    /**
-     * Sets the given list of columns as the columns that will be returned.
-     *
-     * @param columns The list of column names that should be returned.
-     *
-     * @return this
-     */
-    public SupportSQLiteQueryBuilder columns(String[] columns) {
-        mColumns = columns;
-        return this;
-    }
-
-    /**
-     * Sets the arguments for the WHERE clause.
-     *
-     * @param selection The list of selection columns
-     * @param bindArgs The list of bind arguments to match against these columns
-     *
-     * @return this
-     */
-    public SupportSQLiteQueryBuilder selection(String selection, Object[] bindArgs) {
-        mSelection = selection;
-        mBindArgs = bindArgs;
-        return this;
-    }
-
-    /**
-     * Adds a GROUP BY statement.
-     *
-     * @param groupBy The value of the GROUP BY statement.
-     *
-     * @return this
-     */
-    @SuppressWarnings("WeakerAccess")
-    public SupportSQLiteQueryBuilder groupBy(String groupBy) {
-        mGroupBy = groupBy;
-        return this;
-    }
-
-    /**
-     * Adds a HAVING statement. You must also provide {@link #groupBy(String)} for this to work.
-     *
-     * @param having The having clause.
-     *
-     * @return this
-     */
-    public SupportSQLiteQueryBuilder having(String having) {
-        mHaving = having;
-        return this;
-    }
-
-    /**
-     * Adds an ORDER BY statement.
-     *
-     * @param orderBy The order clause.
-     *
-     * @return this
-     */
-    public SupportSQLiteQueryBuilder orderBy(String orderBy) {
-        mOrderBy = orderBy;
-        return this;
-    }
-
-    /**
-     * Adds a LIMIT statement.
-     *
-     * @param limit The limit value.
-     *
-     * @return this
-     */
-    public SupportSQLiteQueryBuilder limit(String limit) {
-        if (!isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
-            throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
-        }
-        mLimit = limit;
-        return this;
-    }
-
-    /**
-     * Creates the {@link SupportSQLiteQuery} that can be passed into
-     * {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
-     *
-     * @return a new query
-     */
-    public SupportSQLiteQuery create() {
-        if (isEmpty(mGroupBy) && !isEmpty(mHaving)) {
-            throw new IllegalArgumentException(
-                    "HAVING clauses are only permitted when using a groupBy clause");
-        }
-        StringBuilder query = new StringBuilder(120);
-
-        query.append("SELECT ");
-        if (mDistinct) {
-            query.append("DISTINCT ");
-        }
-        if (mColumns != null && mColumns.length != 0) {
-            appendColumns(query, mColumns);
-        } else {
-            query.append(" * ");
-        }
-        query.append(" FROM ");
-        query.append(mTable);
-        appendClause(query, " WHERE ", mSelection);
-        appendClause(query, " GROUP BY ", mGroupBy);
-        appendClause(query, " HAVING ", mHaving);
-        appendClause(query, " ORDER BY ", mOrderBy);
-        appendClause(query, " LIMIT ", mLimit);
-
-        return new SimpleSQLiteQuery(query.toString(), mBindArgs);
-    }
-
-    private static void appendClause(StringBuilder s, String name, String clause) {
-        if (!isEmpty(clause)) {
-            s.append(name);
-            s.append(clause);
-        }
-    }
-
-    /**
-     * Add the names that are non-null in columns to s, separating
-     * them with commas.
-     */
-    private static void appendColumns(StringBuilder s, String[] columns) {
-        int n = columns.length;
-
-        for (int i = 0; i < n; i++) {
-            String column = columns[i];
-            if (i > 0) {
-                s.append(", ");
-            }
-            s.append(column);
-        }
-        s.append(' ');
-    }
-
-    private static boolean isEmpty(String input) {
-        return input == null || input.length() == 0;
-    }
-}
diff --git a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java b/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java
deleted file mode 100644
index 5a329d7..0000000
--- a/persistence/db/src/main/java/android/arch/persistence/db/SupportSQLiteStatement.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.db;
-
-/**
- * An interface to map the behavior of {@link android.database.sqlite.SQLiteStatement}.
- */
-@SuppressWarnings("unused")
-public interface SupportSQLiteStatement extends SupportSQLiteProgram {
-    /**
-     * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
-     * CREATE / DROP table, view, trigger, index etc.
-     *
-     * @throws android.database.SQLException If the SQL string is invalid for
-     *         some reason
-     */
-    void execute();
-
-    /**
-     * Execute this SQL statement, if the the number of rows affected by execution of this SQL
-     * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
-     *
-     * @return the number of rows affected by this SQL statement execution.
-     * @throws android.database.SQLException If the SQL string is invalid for
-     *         some reason
-     */
-    int executeUpdateDelete();
-
-    /**
-     * Execute this SQL statement and return the ID of the row inserted due to this call.
-     * The SQL statement should be an INSERT for this to be a useful call.
-     *
-     * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
-     *
-     * @throws android.database.SQLException If the SQL string is invalid for
-     *         some reason
-     */
-    long executeInsert();
-
-    /**
-     * Execute a statement that returns a 1 by 1 table with a numeric value.
-     * For example, SELECT COUNT(*) FROM table;
-     *
-     * @return The result of the query.
-     *
-     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
-     */
-    long simpleQueryForLong();
-    /**
-     * Execute a statement that returns a 1 by 1 table with a text value.
-     * For example, SELECT COUNT(*) FROM table;
-     *
-     * @return The result of the query.
-     *
-     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
-     */
-    String simpleQueryForString();
-}
diff --git a/persistence/db/src/main/java/androidx/sqlite/db/SimpleSQLiteQuery.java b/persistence/db/src/main/java/androidx/sqlite/db/SimpleSQLiteQuery.java
new file mode 100644
index 0000000..bdcbb9f
--- /dev/null
+++ b/persistence/db/src/main/java/androidx/sqlite/db/SimpleSQLiteQuery.java
@@ -0,0 +1,107 @@
+/*
+ * 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 androidx.sqlite.db;
+
+/**
+ * A basic implementation of {@link SupportSQLiteQuery} which receives a query and its args and
+ * binds args based on the passed in Object type.
+ */
+public final class SimpleSQLiteQuery implements SupportSQLiteQuery {
+    private final String mQuery;
+    private final Object[] mBindArgs;
+
+    /**
+     * Creates an SQL query with the sql string and the bind arguments.
+     *
+     * @param query    The query string, can include bind arguments (.e.g ?).
+     * @param bindArgs The bind argument value that will replace the placeholders in the query.
+     */
+    public SimpleSQLiteQuery(String query, Object[] bindArgs) {
+        mQuery = query;
+        mBindArgs = bindArgs;
+    }
+
+    /**
+     * Creates an SQL query without any bind arguments.
+     *
+     * @param query The SQL query to execute. Cannot include bind parameters.
+     */
+    public SimpleSQLiteQuery(String query) {
+        this(query, null);
+    }
+
+    @Override
+    public String getSql() {
+        return mQuery;
+    }
+
+    @Override
+    public void bindTo(SupportSQLiteProgram statement) {
+        bind(statement, mBindArgs);
+    }
+
+    @Override
+    public int getArgCount() {
+        return mBindArgs.length;
+    }
+
+    /**
+     * Binds the given arguments into the given sqlite statement.
+     *
+     * @param statement The sqlite statement
+     * @param bindArgs  The list of bind arguments
+     */
+    public static void bind(SupportSQLiteProgram statement, Object[] bindArgs) {
+        if (bindArgs == null) {
+            return;
+        }
+        final int limit = bindArgs.length;
+        for (int i = 0; i < limit; i++) {
+            final Object arg = bindArgs[i];
+            bind(statement, i + 1, arg);
+        }
+    }
+
+    private static void bind(SupportSQLiteProgram statement, int index, Object arg) {
+        // extracted from android.database.sqlite.SQLiteConnection
+        if (arg == null) {
+            statement.bindNull(index);
+        } else if (arg instanceof byte[]) {
+            statement.bindBlob(index, (byte[]) arg);
+        } else if (arg instanceof Float) {
+            statement.bindDouble(index, (Float) arg);
+        } else if (arg instanceof Double) {
+            statement.bindDouble(index, (Double) arg);
+        } else if (arg instanceof Long) {
+            statement.bindLong(index, (Long) arg);
+        } else if (arg instanceof Integer) {
+            statement.bindLong(index, (Integer) arg);
+        } else if (arg instanceof Short) {
+            statement.bindLong(index, (Short) arg);
+        } else if (arg instanceof Byte) {
+            statement.bindLong(index, (Byte) arg);
+        } else if (arg instanceof String) {
+            statement.bindString(index, (String) arg);
+        } else if (arg instanceof Boolean) {
+            statement.bindLong(index, ((Boolean) arg) ? 1 : 0);
+        } else {
+            throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index
+                    + " Supported types: null, byte[], float, double, long, int, short, byte,"
+                    + " string");
+        }
+    }
+}
diff --git a/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.java b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.java
new file mode 100644
index 0000000..961944b
--- /dev/null
+++ b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.util.Pair;
+
+import androidx.annotation.RequiresApi;
+
+import java.io.Closeable;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A database abstraction which removes the framework dependency and allows swapping underlying
+ * sql versions. It mimics the behavior of {@link android.database.sqlite.SQLiteDatabase}
+ */
+@SuppressWarnings("unused")
+public interface SupportSQLiteDatabase extends Closeable {
+    /**
+     * Compiles the given SQL statement.
+     *
+     * @param sql The sql query.
+     * @return Compiled statement.
+     */
+    SupportSQLiteStatement compileStatement(String sql);
+
+    /**
+     * Begins a transaction in EXCLUSIVE mode.
+     * <p>
+     * Transactions can be nested.
+     * When the outer transaction is ended all of
+     * the work done in that transaction and all of the nested transactions will be committed or
+     * rolled back. The changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
+     * </p>
+     * <p>Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransaction();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    void beginTransaction();
+
+    /**
+     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
+     * the outer transaction is ended all of the work done in that transaction
+     * and all of the nested transactions will be committed or rolled back. The
+     * changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they
+     * will be committed.
+     * <p>
+     * Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionNonExclusive();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    void beginTransactionNonExclusive();
+
+    /**
+     * Begins a transaction in EXCLUSIVE mode.
+     * <p>
+     * Transactions can be nested.
+     * When the outer transaction is ended all of
+     * the work done in that transaction and all of the nested transactions will be committed or
+     * rolled back. The changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
+     * </p>
+     * <p>Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionWithListener(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     *
+     * @param transactionListener listener that should be notified when the transaction begins,
+     *                            commits, or is rolled back, either explicitly or by a call to
+     *                            {@link #yieldIfContendedSafely}.
+     */
+    void beginTransactionWithListener(SQLiteTransactionListener transactionListener);
+
+    /**
+     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
+     * the outer transaction is ended all of the work done in that transaction
+     * and all of the nested transactions will be committed or rolled back. The
+     * changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they
+     * will be committed.
+     * <p>
+     * Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionWithListenerNonExclusive(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     *
+     * @param transactionListener listener that should be notified when the
+     *                            transaction begins, commits, or is rolled back, either
+     *                            explicitly or by a call to {@link #yieldIfContendedSafely}.
+     */
+    void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener);
+
+    /**
+     * End a transaction. See beginTransaction for notes about how to use this and when transactions
+     * are committed and rolled back.
+     */
+    void endTransaction();
+
+    /**
+     * Marks the current transaction as successful. Do not do any more database work between
+     * calling this and calling endTransaction. Do as little non-database work as possible in that
+     * situation too. If any errors are encountered between this and endTransaction the transaction
+     * will still be committed.
+     *
+     * @throws IllegalStateException if the current thread is not in a transaction or the
+     *                               transaction is already marked as successful.
+     */
+    void setTransactionSuccessful();
+
+    /**
+     * Returns true if the current thread has a transaction pending.
+     *
+     * @return True if the current thread is in a transaction.
+     */
+    boolean inTransaction();
+
+    /**
+     * Returns true if the current thread is holding an active connection to the database.
+     * <p>
+     * The name of this method comes from a time when having an active connection
+     * to the database meant that the thread was holding an actual lock on the
+     * database.  Nowadays, there is no longer a true "database lock" although threads
+     * may block if they cannot acquire a database connection to perform a
+     * particular operation.
+     * </p>
+     *
+     * @return True if the current thread is holding an active connection to the database.
+     */
+    boolean isDbLockedByCurrentThread();
+
+    /**
+     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
+     * successful so far. Do not call setTransactionSuccessful before calling this. When this
+     * returns a new transaction will have been created but not marked as successful. This assumes
+     * that there are no nested transactions (beginTransaction has only been called once) and will
+     * throw an exception if that is not the case.
+     *
+     * @return true if the transaction was yielded
+     */
+    boolean yieldIfContendedSafely();
+
+    /**
+     * Temporarily end the transaction to let other threads run. The transaction is assumed to be
+     * successful so far. Do not call setTransactionSuccessful before calling this. When this
+     * returns a new transaction will have been created but not marked as successful. This assumes
+     * that there are no nested transactions (beginTransaction has only been called once) and will
+     * throw an exception if that is not the case.
+     *
+     * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if
+     *                             the lock was actually yielded. This will allow other background
+     *                             threads to make some
+     *                             more progress than they would if we started the transaction
+     *                             immediately.
+     * @return true if the transaction was yielded
+     */
+    boolean yieldIfContendedSafely(long sleepAfterYieldDelay);
+
+    /**
+     * Gets the database version.
+     *
+     * @return the database version
+     */
+    int getVersion();
+
+    /**
+     * Sets the database version.
+     *
+     * @param version the new database version
+     */
+    void setVersion(int version);
+
+    /**
+     * Returns the maximum size the database may grow to.
+     *
+     * @return the new maximum database size
+     */
+    long getMaximumSize();
+
+    /**
+     * Sets the maximum size the database will grow to. The maximum size cannot
+     * be set below the current size.
+     *
+     * @param numBytes the maximum database size, in bytes
+     * @return the new maximum database size
+     */
+    long setMaximumSize(long numBytes);
+
+    /**
+     * Returns the current database page size, in bytes.
+     *
+     * @return the database page size, in bytes
+     */
+    long getPageSize();
+
+    /**
+     * Sets the database page size. The page size must be a power of two. This
+     * method does not work if any data has been written to the database file,
+     * and must be called right after the database has been created.
+     *
+     * @param numBytes the database page size, in bytes
+     */
+    void setPageSize(long numBytes);
+
+    /**
+     * Runs the given query on the database. If you would like to have typed bind arguments,
+     * use {@link #query(SupportSQLiteQuery)}.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see #query(SupportSQLiteQuery)
+     */
+    Cursor query(String query);
+
+    /**
+     * Runs the given query on the database. If you would like to have bind arguments,
+     * use {@link #query(SupportSQLiteQuery)}.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @param bindArgs The query arguments to bind.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see #query(SupportSQLiteQuery)
+     */
+    Cursor query(String query, Object[] bindArgs);
+
+    /**
+     * Runs the given query on the database.
+     * <p>
+     * This class allows using type safe sql program bindings while running queries.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     * @see SimpleSQLiteQuery
+     */
+    Cursor query(SupportSQLiteQuery query);
+
+    /**
+     * Runs the given query on the database.
+     * <p>
+     * This class allows using type safe sql program bindings while running queries.
+     *
+     * @param query The SQL query that includes the query and can bind into a given compiled
+     *              program.
+     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A {@link Cursor} object, which is positioned before the first entry. Note that
+     * {@link Cursor}s are not synchronized, see the documentation for more details.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal);
+
+    /**
+     * Convenience method for inserting a row into the database.
+     *
+     * @param table          the table to insert the row into
+     * @param values         this map contains the initial column values for the
+     *                       row. The keys should be the column names and the values the
+     *                       column values
+     * @param conflictAlgorithm for insert conflict resolver. One of
+     * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK},
+     * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL},
+     * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}.
+     * @return the row ID of the newly inserted row, or -1 if an error occurred
+     * @throws SQLException If the insert fails
+     */
+    long insert(String table, int conflictAlgorithm, ContentValues values) throws SQLException;
+
+    /**
+     * Convenience method for deleting rows in the database.
+     *
+     * @param table       the table to delete from
+     * @param whereClause the optional WHERE clause to apply when deleting.
+     *                    Passing null will delete all rows.
+     * @param whereArgs   You may include ?s in the where clause, which
+     *                    will be replaced by the values from whereArgs. The values
+     *                    will be bound as Strings.
+     * @return the number of rows affected if a whereClause is passed in, 0
+     * otherwise. To remove all rows and get a count pass "1" as the
+     * whereClause.
+     */
+    int delete(String table, String whereClause, Object[] whereArgs);
+
+    /**
+     * Convenience method for updating rows in the database.
+     *
+     * @param table       the table to update in
+     * @param conflictAlgorithm for update conflict resolver. One of
+     * {@link SQLiteDatabase#CONFLICT_NONE}, {@link SQLiteDatabase#CONFLICT_ROLLBACK},
+     * {@link SQLiteDatabase#CONFLICT_ABORT}, {@link SQLiteDatabase#CONFLICT_FAIL},
+     * {@link SQLiteDatabase#CONFLICT_IGNORE}, {@link SQLiteDatabase#CONFLICT_REPLACE}.
+     * @param values      a map from column names to new column values. null is a
+     *                    valid value that will be translated to NULL.
+     * @param whereClause the optional WHERE clause to apply when updating.
+     *                    Passing null will update all rows.
+     * @param whereArgs   You may include ?s in the where clause, which
+     *                    will be replaced by the values from whereArgs. The values
+     *                    will be bound as Strings.
+     * @return the number of rows affected
+     */
+    int update(String table, int conflictAlgorithm,
+            ContentValues values, String whereClause, Object[] whereArgs);
+
+    /**
+     * Execute a single SQL statement that does not return any data.
+     * <p>
+     * When using {@link #enableWriteAheadLogging()}, journal_mode is
+     * automatically managed by this class. So, do not set journal_mode
+     * using "PRAGMA journal_mode'<value>" statement if your app is using
+     * {@link #enableWriteAheadLogging()}
+     * </p>
+     *
+     * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are
+     *            not supported.
+     * @throws SQLException if the SQL string is invalid
+     * @see #query(SupportSQLiteQuery)
+     */
+    void execSQL(String sql) throws SQLException;
+
+    /**
+     * Execute a single SQL statement that does not return any data.
+     * <p>
+     * When using {@link #enableWriteAheadLogging()}, journal_mode is
+     * automatically managed by this class. So, do not set journal_mode
+     * using "PRAGMA journal_mode'<value>" statement if your app is using
+     * {@link #enableWriteAheadLogging()}
+     * </p>
+     *
+     * @param sql      the SQL statement to be executed. Multiple statements separated by semicolons
+     *                 are
+     *                 not supported.
+     * @param bindArgs only byte[], String, Long and Double are supported in selectionArgs.
+     * @throws SQLException if the SQL string is invalid
+     * @see #query(SupportSQLiteQuery)
+     */
+    void execSQL(String sql, Object[] bindArgs) throws SQLException;
+
+    /**
+     * Returns true if the database is opened as read only.
+     *
+     * @return True if database is opened as read only.
+     */
+    boolean isReadOnly();
+
+    /**
+     * Returns true if the database is currently open.
+     *
+     * @return True if the database is currently open (has not been closed).
+     */
+    boolean isOpen();
+
+    /**
+     * Returns true if the new version code is greater than the current database version.
+     *
+     * @param newVersion The new version code.
+     * @return True if the new version code is greater than the current database version.
+     */
+    boolean needUpgrade(int newVersion);
+
+    /**
+     * Gets the path to the database file.
+     *
+     * @return The path to the database file.
+     */
+    String getPath();
+
+    /**
+     * Sets the locale for this database.  Does nothing if this database has
+     * the {@link SQLiteDatabase#NO_LOCALIZED_COLLATORS} flag set or was opened read only.
+     *
+     * @param locale The new locale.
+     * @throws SQLException if the locale could not be set.  The most common reason
+     *                      for this is that there is no collator available for the locale you
+     *                      requested.
+     *                      In this case the database remains unchanged.
+     */
+    void setLocale(Locale locale);
+
+    /**
+     * Sets the maximum size of the prepared-statement cache for this database.
+     * (size of the cache = number of compiled-sql-statements stored in the cache).
+     * <p>
+     * Maximum cache size can ONLY be increased from its current size (default = 10).
+     * If this method is called with smaller size than the current maximum value,
+     * then IllegalStateException is thrown.
+     * <p>
+     * This method is thread-safe.
+     *
+     * @param cacheSize the size of the cache. can be (0 to
+     *                  {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE})
+     * @throws IllegalStateException if input cacheSize gt;
+     *                               {@link SQLiteDatabase#MAX_SQL_CACHE_SIZE}.
+     */
+    void setMaxSqlCacheSize(int cacheSize);
+
+    /**
+     * Sets whether foreign key constraints are enabled for the database.
+     * <p>
+     * By default, foreign key constraints are not enforced by the database.
+     * This method allows an application to enable foreign key constraints.
+     * It must be called each time the database is opened to ensure that foreign
+     * key constraints are enabled for the session.
+     * </p><p>
+     * A good time to call this method is right after calling {@code #openOrCreateDatabase}
+     * or in the {@link SupportSQLiteOpenHelper.Callback#onConfigure} callback.
+     * </p><p>
+     * When foreign key constraints are disabled, the database does not check whether
+     * changes to the database will violate foreign key constraints.  Likewise, when
+     * foreign key constraints are disabled, the database will not execute cascade
+     * delete or update triggers.  As a result, it is possible for the database
+     * state to become inconsistent.  To perform a database integrity check,
+     * call {@link #isDatabaseIntegrityOk}.
+     * </p><p>
+     * This method must not be called while a transaction is in progress.
+     * </p><p>
+     * See also <a href="http://sqlite.org/foreignkeys.html">SQLite Foreign Key Constraints</a>
+     * for more details about foreign key constraint support.
+     * </p>
+     *
+     * @param enable True to enable foreign key constraints, false to disable them.
+     * @throws IllegalStateException if the are transactions is in progress
+     *                               when this method is called.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    void setForeignKeyConstraintsEnabled(boolean enable);
+
+    /**
+     * This method enables parallel execution of queries from multiple threads on the
+     * same database.  It does this by opening multiple connections to the database
+     * and using a different database connection for each query.  The database
+     * journal mode is also changed to enable writes to proceed concurrently with reads.
+     * <p>
+     * When write-ahead logging is not enabled (the default), it is not possible for
+     * reads and writes to occur on the database at the same time.  Before modifying the
+     * database, the writer implicitly acquires an exclusive lock on the database which
+     * prevents readers from accessing the database until the write is completed.
+     * </p><p>
+     * In contrast, when write-ahead logging is enabled (by calling this method), write
+     * operations occur in a separate log file which allows reads to proceed concurrently.
+     * While a write is in progress, readers on other threads will perceive the state
+     * of the database as it was before the write began.  When the write completes, readers
+     * on other threads will then perceive the new state of the database.
+     * </p><p>
+     * It is a good idea to enable write-ahead logging whenever a database will be
+     * concurrently accessed and modified by multiple threads at the same time.
+     * However, write-ahead logging uses significantly more memory than ordinary
+     * journaling because there are multiple connections to the same database.
+     * So if a database will only be used by a single thread, or if optimizing
+     * concurrency is not very important, then write-ahead logging should be disabled.
+     * </p><p>
+     * After calling this method, execution of queries in parallel is enabled as long as
+     * the database remains open.  To disable execution of queries in parallel, either
+     * call {@link #disableWriteAheadLogging} or close the database and reopen it.
+     * </p><p>
+     * The maximum number of connections used to execute queries in parallel is
+     * dependent upon the device memory and possibly other properties.
+     * </p><p>
+     * If a query is part of a transaction, then it is executed on the same database handle the
+     * transaction was begun.
+     * </p><p>
+     * Writers should use {@link #beginTransactionNonExclusive()} or
+     * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)}
+     * to start a transaction.  Non-exclusive mode allows database file to be in readable
+     * by other threads executing queries.
+     * </p><p>
+     * If the database has any attached databases, then execution of queries in parallel is NOT
+     * possible.  Likewise, write-ahead logging is not supported for read-only databases
+     * or memory databases.  In such cases, {@code enableWriteAheadLogging} returns false.
+     * </p><p>
+     * The best way to enable write-ahead logging is to pass the
+     * {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag to
+     * {@link SQLiteDatabase#openDatabase}.  This is more efficient than calling
+     * <code><pre>
+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
+     *             myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * </pre></code>
+     * </p><p>
+     * Another way to enable write-ahead logging is to call {@code enableWriteAheadLogging}
+     * after opening the database.
+     * <code><pre>
+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * </pre></code>
+     * </p><p>
+     * See also <a href="http://sqlite.org/wal.html">SQLite Write-Ahead Logging</a> for
+     * more details about how write-ahead logging works.
+     * </p>
+     *
+     * @return True if write-ahead logging is enabled.
+     * @throws IllegalStateException if there are transactions in progress at the
+     *                               time this method is called.  WAL mode can only be changed when
+     *                               there are no
+     *                               transactions in progress.
+     * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING
+     * @see #disableWriteAheadLogging
+     */
+    boolean enableWriteAheadLogging();
+
+    /**
+     * This method disables the features enabled by {@link #enableWriteAheadLogging()}.
+     *
+     * @throws IllegalStateException if there are transactions in progress at the
+     *                               time this method is called.  WAL mode can only be changed when
+     *                               there are no
+     *                               transactions in progress.
+     * @see #enableWriteAheadLogging
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    void disableWriteAheadLogging();
+
+    /**
+     * Returns true if write-ahead logging has been enabled for this database.
+     *
+     * @return True if write-ahead logging has been enabled for this database.
+     * @see #enableWriteAheadLogging
+     * @see SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    boolean isWriteAheadLoggingEnabled();
+
+    /**
+     * Returns list of full path names of all attached databases including the main database
+     * by executing 'pragma database_list' on the database.
+     *
+     * @return ArrayList of pairs of (database name, database file path) or null if the database
+     * is not open.
+     */
+    List<Pair<String, String>> getAttachedDbs();
+
+    /**
+     * Runs 'pragma integrity_check' on the given database (and all the attached databases)
+     * and returns true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise.
+     * <p>
+     * If the result is false, then this method logs the errors reported by the integrity_check
+     * command execution.
+     * <p>
+     * Note that 'pragma integrity_check' on a database can take a long time.
+     *
+     * @return true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise.
+     */
+    boolean isDatabaseIntegrityOk();
+}
diff --git a/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
new file mode 100644
index 0000000..322f9d5
--- /dev/null
+++ b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.os.Build;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}.
+ * Note that since that class requires overriding certain methods, support implementation
+ * uses {@link Factory#create(Configuration)} to create this and {@link Callback} to implement
+ * the methods that should be overridden.
+ */
+@SuppressWarnings("unused")
+public interface SupportSQLiteOpenHelper {
+    /**
+     * Return the name of the SQLite database being opened, as given to
+     * the constructor.
+     */
+    String getDatabaseName();
+
+    /**
+     * Enables or disables the use of write-ahead logging for the database.
+     *
+     * Write-ahead logging cannot be used with read-only databases so the value of
+     * this flag is ignored if the database is opened read-only.
+     *
+     * @param enabled True if write-ahead logging should be enabled, false if it
+     *                should be disabled.
+     * @see SupportSQLiteDatabase#enableWriteAheadLogging()
+     */
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    void setWriteAheadLoggingEnabled(boolean enabled);
+
+    /**
+     * Create and/or open a database that will be used for reading and writing.
+     * The first time this is called, the database will be opened and
+     * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be
+     * called.
+     *
+     * <p>Once opened successfully, the database is cached, so you can
+     * call this method every time you need to write to the database.
+     * (Make sure to call {@link #close} when you no longer need the database.)
+     * Errors such as bad permissions or a full disk may cause this method
+     * to fail, but future attempts may succeed if the problem is fixed.</p>
+     *
+     * <p class="caution">Database upgrade may take a long time, you
+     * should not call this method from the application main thread, including
+     * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
+     *
+     * @return a read/write database object valid until {@link #close} is called
+     * @throws SQLiteException if the database cannot be opened for writing
+     */
+    SupportSQLiteDatabase getWritableDatabase();
+
+    /**
+     * Create and/or open a database.  This will be the same object returned by
+     * {@link #getWritableDatabase} unless some problem, such as a full disk,
+     * requires the database to be opened read-only.  In that case, a read-only
+     * database object will be returned.  If the problem is fixed, a future call
+     * to {@link #getWritableDatabase} may succeed, in which case the read-only
+     * database object will be closed and the read/write object will be returned
+     * in the future.
+     *
+     * <p class="caution">Like {@link #getWritableDatabase}, this method may
+     * take a long time to return, so you should not call it from the
+     * application main thread, including from
+     * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
+     *
+     * @return a database object valid until {@link #getWritableDatabase}
+     * or {@link #close} is called.
+     * @throws SQLiteException if the database cannot be opened
+     */
+    SupportSQLiteDatabase getReadableDatabase();
+
+    /**
+     * Close any open database object.
+     */
+    void close();
+
+    /**
+     * 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.
+         * <p>
+         * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade},
+         * or {@link #onOpen} are called. It should not modify the database except to configure the
+         * database connection as required.
+         * </p>
+         * <p>
+         * This method should only call methods that configure the parameters of the database
+         * connection, such as {@link SupportSQLiteDatabase#enableWriteAheadLogging}
+         * {@link SupportSQLiteDatabase#setForeignKeyConstraintsEnabled},
+         * {@link SupportSQLiteDatabase#setLocale},
+         * {@link SupportSQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
+         * </p>
+         *
+         * @param db The database.
+         */
+        public void onConfigure(SupportSQLiteDatabase db) {
+
+        }
+
+        /**
+         * Called when the database is created for the first time. This is where the
+         * creation of tables and the initial population of the tables should happen.
+         *
+         * @param db The database.
+         */
+        public abstract void onCreate(SupportSQLiteDatabase db);
+
+        /**
+         * Called when the database needs to be upgraded. The implementation
+         * should use this method to drop tables, add tables, or do anything else it
+         * needs to upgrade to the new schema version.
+         *
+         * <p>
+         * The SQLite ALTER TABLE documentation can be found
+         * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
+         * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
+         * you can use ALTER TABLE to rename the old table, then create the new table and then
+         * populate the new table with the contents of the old table.
+         * </p><p>
+         * This method executes within a transaction.  If an exception is thrown, all changes
+         * will automatically be rolled back.
+         * </p>
+         *
+         * @param db         The database.
+         * @param oldVersion The old database version.
+         * @param newVersion The new database version.
+         */
+        public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);
+
+        /**
+         * Called when the database needs to be downgraded. This is strictly similar to
+         * {@link #onUpgrade} method, but is called whenever current version is newer than requested
+         * one.
+         * However, this method is not abstract, so it is not mandatory for a customer to
+         * implement it. If not overridden, default implementation will reject downgrade and
+         * throws SQLiteException
+         *
+         * <p>
+         * This method executes within a transaction.  If an exception is thrown, all changes
+         * will automatically be rolled back.
+         * </p>
+         *
+         * @param db         The database.
+         * @param oldVersion The old database version.
+         * @param newVersion The new database version.
+         */
+        public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
+            throw new SQLiteException("Can't downgrade database from version "
+                    + oldVersion + " to " + newVersion);
+        }
+
+        /**
+         * Called when the database has been opened.  The implementation
+         * should check {@link SupportSQLiteDatabase#isReadOnly} before updating the
+         * database.
+         * <p>
+         * This method is called after the database connection has been configured
+         * and after the database schema has been created, upgraded or downgraded as necessary.
+         * If the database connection must be configured in some way before the schema
+         * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
+         * </p>
+         *
+         * @param db The database.
+         */
+        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);
+            }
+        }
+    }
+
+    /**
+     * The configuration to create an SQLite open helper object using {@link Factory}.
+     */
+    @SuppressWarnings("WeakerAccess")
+    class Configuration {
+        /**
+         * Context to use to open or create the database.
+         */
+        @NonNull
+        public final Context context;
+        /**
+         * Name of the database file, or null for an in-memory database.
+         */
+        @Nullable
+        public final String name;
+        /**
+         * The callback class to handle creation, upgrade and downgrade.
+         */
+        @NonNull
+        public final SupportSQLiteOpenHelper.Callback callback;
+
+        Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
+            this.context = context;
+            this.name = name;
+            this.callback = callback;
+        }
+
+        /**
+         * Creates a new Configuration.Builder to create an instance of Configuration.
+         *
+         * @param context to use to open or create the database.
+         */
+        public static Builder builder(Context context) {
+            return new Builder(context);
+        }
+
+        /**
+         * Builder class for {@link Configuration}.
+         */
+        public static class Builder {
+            Context mContext;
+            String mName;
+            SupportSQLiteOpenHelper.Callback mCallback;
+
+            public Configuration build() {
+                if (mCallback == null) {
+                    throw new IllegalArgumentException("Must set a callback to create the"
+                            + " configuration.");
+                }
+                if (mContext == null) {
+                    throw new IllegalArgumentException("Must set a non-null context to create"
+                            + " the configuration.");
+                }
+                return new Configuration(mContext, mName, mCallback);
+            }
+
+            Builder(@NonNull Context context) {
+                mContext = context;
+            }
+
+            /**
+             * @param name Name of the database file, or null for an in-memory database.
+             * @return This
+             */
+            public Builder name(@Nullable String name) {
+                mName = name;
+                return this;
+            }
+
+            /**
+             * @param callback The callback class to handle creation, upgrade and downgrade.
+             * @return this
+             */
+            public Builder callback(@NonNull Callback callback) {
+                mCallback = callback;
+                return this;
+            }
+        }
+    }
+
+    /**
+     * Factory class to create instances of {@link SupportSQLiteOpenHelper} using
+     * {@link Configuration}.
+     */
+    interface Factory {
+        /**
+         * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration.
+         *
+         * @param configuration The configuration to use while creating the open helper.
+         *
+         * @return A SupportSQLiteOpenHelper which can be used to open a database.
+         */
+        SupportSQLiteOpenHelper create(Configuration configuration);
+    }
+}
diff --git a/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteProgram.java b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteProgram.java
new file mode 100644
index 0000000..9eeb9b0
--- /dev/null
+++ b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteProgram.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db;
+
+import java.io.Closeable;
+
+/**
+ * An interface to map the behavior of {@link android.database.sqlite.SQLiteProgram}.
+ */
+
+@SuppressWarnings("unused")
+public interface SupportSQLiteProgram extends Closeable {
+    /**
+     * Bind a NULL value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind null to
+     */
+    void bindNull(int index);
+
+    /**
+     * Bind a long value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *addToBindArgs
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    void bindLong(int index, long value);
+
+    /**
+     * Bind a double value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind
+     */
+    void bindDouble(int index, double value);
+
+    /**
+     * Bind a String value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind, must not be null
+     */
+    void bindString(int index, String value);
+
+    /**
+     * Bind a byte array value to this statement. The value remains bound until
+     * {@link #clearBindings} is called.
+     *
+     * @param index The 1-based index to the parameter to bind
+     * @param value The value to bind, must not be null
+     */
+    void bindBlob(int index, byte[] value);
+
+    /**
+     * Clears all existing bindings. Unset bindings are treated as NULL.
+     */
+    void clearBindings();
+}
diff --git a/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteQuery.java b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteQuery.java
new file mode 100644
index 0000000..b631528
--- /dev/null
+++ b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteQuery.java
@@ -0,0 +1,46 @@
+/*
+ * 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 androidx.sqlite.db;
+
+/**
+ * A query with typed bindings. It is better to use this API instead of
+ * {@link android.database.sqlite.SQLiteDatabase#rawQuery(String, String[])} because it allows
+ * binding type safe parameters.
+ */
+public interface SupportSQLiteQuery {
+    /**
+     * The SQL query. This query can have placeholders(?) for bind arguments.
+     *
+     * @return The SQL query to compile
+     */
+    String getSql();
+
+    /**
+     * Callback to bind the query parameters to the compiled statement.
+     *
+     * @param statement The compiled statement
+     */
+    void bindTo(SupportSQLiteProgram statement);
+
+    /**
+     * Returns the number of arguments in this query. This is equal to the number of placeholders
+     * in the query string. See: https://www.sqlite.org/c3ref/bind_blob.html for details.
+     *
+     * @return The number of arguments in the query.
+     */
+    int getArgCount();
+}
diff --git a/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteQueryBuilder.java b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteQueryBuilder.java
new file mode 100644
index 0000000..a438fa8
--- /dev/null
+++ b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteQueryBuilder.java
@@ -0,0 +1,202 @@
+/*
+ * 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 androidx.sqlite.db;
+
+import java.util.regex.Pattern;
+
+/**
+ * A simple query builder to create SQL SELECT queries.
+ */
+@SuppressWarnings("unused")
+public final class SupportSQLiteQueryBuilder {
+    private static final Pattern sLimitPattern =
+            Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
+
+    private boolean mDistinct = false;
+    private final String mTable;
+    private String[] mColumns = null;
+    private String mSelection;
+    private Object[] mBindArgs;
+    private String mGroupBy = null;
+    private String mHaving = null;
+    private String mOrderBy = null;
+    private String mLimit = null;
+
+    /**
+     * Creates a query for the given table name.
+     *
+     * @param tableName The table name(s) to query.
+     *
+     * @return A builder to create a query.
+     */
+    public static SupportSQLiteQueryBuilder builder(String tableName) {
+        return new SupportSQLiteQueryBuilder(tableName);
+    }
+
+    private SupportSQLiteQueryBuilder(String table) {
+        mTable = table;
+    }
+
+    /**
+     * Adds DISTINCT keyword to the query.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder distinct() {
+        mDistinct = true;
+        return this;
+    }
+
+    /**
+     * Sets the given list of columns as the columns that will be returned.
+     *
+     * @param columns The list of column names that should be returned.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder columns(String[] columns) {
+        mColumns = columns;
+        return this;
+    }
+
+    /**
+     * Sets the arguments for the WHERE clause.
+     *
+     * @param selection The list of selection columns
+     * @param bindArgs The list of bind arguments to match against these columns
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder selection(String selection, Object[] bindArgs) {
+        mSelection = selection;
+        mBindArgs = bindArgs;
+        return this;
+    }
+
+    /**
+     * Adds a GROUP BY statement.
+     *
+     * @param groupBy The value of the GROUP BY statement.
+     *
+     * @return this
+     */
+    @SuppressWarnings("WeakerAccess")
+    public SupportSQLiteQueryBuilder groupBy(String groupBy) {
+        mGroupBy = groupBy;
+        return this;
+    }
+
+    /**
+     * Adds a HAVING statement. You must also provide {@link #groupBy(String)} for this to work.
+     *
+     * @param having The having clause.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder having(String having) {
+        mHaving = having;
+        return this;
+    }
+
+    /**
+     * Adds an ORDER BY statement.
+     *
+     * @param orderBy The order clause.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder orderBy(String orderBy) {
+        mOrderBy = orderBy;
+        return this;
+    }
+
+    /**
+     * Adds a LIMIT statement.
+     *
+     * @param limit The limit value.
+     *
+     * @return this
+     */
+    public SupportSQLiteQueryBuilder limit(String limit) {
+        if (!isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
+            throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
+        }
+        mLimit = limit;
+        return this;
+    }
+
+    /**
+     * Creates the {@link SupportSQLiteQuery} that can be passed into
+     * {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
+     *
+     * @return a new query
+     */
+    public SupportSQLiteQuery create() {
+        if (isEmpty(mGroupBy) && !isEmpty(mHaving)) {
+            throw new IllegalArgumentException(
+                    "HAVING clauses are only permitted when using a groupBy clause");
+        }
+        StringBuilder query = new StringBuilder(120);
+
+        query.append("SELECT ");
+        if (mDistinct) {
+            query.append("DISTINCT ");
+        }
+        if (mColumns != null && mColumns.length != 0) {
+            appendColumns(query, mColumns);
+        } else {
+            query.append(" * ");
+        }
+        query.append(" FROM ");
+        query.append(mTable);
+        appendClause(query, " WHERE ", mSelection);
+        appendClause(query, " GROUP BY ", mGroupBy);
+        appendClause(query, " HAVING ", mHaving);
+        appendClause(query, " ORDER BY ", mOrderBy);
+        appendClause(query, " LIMIT ", mLimit);
+
+        return new SimpleSQLiteQuery(query.toString(), mBindArgs);
+    }
+
+    private static void appendClause(StringBuilder s, String name, String clause) {
+        if (!isEmpty(clause)) {
+            s.append(name);
+            s.append(clause);
+        }
+    }
+
+    /**
+     * Add the names that are non-null in columns to s, separating
+     * them with commas.
+     */
+    private static void appendColumns(StringBuilder s, String[] columns) {
+        int n = columns.length;
+
+        for (int i = 0; i < n; i++) {
+            String column = columns[i];
+            if (i > 0) {
+                s.append(", ");
+            }
+            s.append(column);
+        }
+        s.append(' ');
+    }
+
+    private static boolean isEmpty(String input) {
+        return input == null || input.length() == 0;
+    }
+}
diff --git a/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteStatement.java b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteStatement.java
new file mode 100644
index 0000000..1b136d5
--- /dev/null
+++ b/persistence/db/src/main/java/androidx/sqlite/db/SupportSQLiteStatement.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.sqlite.db;
+
+/**
+ * An interface to map the behavior of {@link android.database.sqlite.SQLiteStatement}.
+ */
+@SuppressWarnings("unused")
+public interface SupportSQLiteStatement extends SupportSQLiteProgram {
+    /**
+     * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
+     * CREATE / DROP table, view, trigger, index etc.
+     *
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    void execute();
+
+    /**
+     * Execute this SQL statement, if the the number of rows affected by execution of this SQL
+     * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
+     *
+     * @return the number of rows affected by this SQL statement execution.
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    int executeUpdateDelete();
+
+    /**
+     * Execute this SQL statement and return the ID of the row inserted due to this call.
+     * The SQL statement should be an INSERT for this to be a useful call.
+     *
+     * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
+     *
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    long executeInsert();
+
+    /**
+     * Execute a statement that returns a 1 by 1 table with a numeric value.
+     * For example, SELECT COUNT(*) FROM table;
+     *
+     * @return The result of the query.
+     *
+     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
+     */
+    long simpleQueryForLong();
+    /**
+     * Execute a statement that returns a 1 by 1 table with a text value.
+     * For example, SELECT COUNT(*) FROM table;
+     *
+     * @return The result of the query.
+     *
+     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
+     */
+    String simpleQueryForString();
+}
diff --git a/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java b/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java
index 1c9ebe0..2fa0e1b 100644
--- a/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java
+++ b/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java
@@ -62,7 +62,7 @@
     protected void onBindDialogView(View view) {
         super.onBindDialogView(view);
 
-        mEditText = (EditText) view.findViewById(android.R.id.edit);
+        mEditText = view.findViewById(android.R.id.edit);
 
         if (mEditText == null) {
             throw new IllegalStateException("Dialog view must contain an EditText with id"
@@ -70,6 +70,8 @@
         }
 
         mEditText.setText(mText);
+        // Place cursor at the end
+        mEditText.setSelection(mText.length());
     }
 
     private EditTextPreference getEditTextPreference() {
diff --git a/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java b/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java
index d02b95b..45c62be 100644
--- a/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java
+++ b/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java
@@ -62,7 +62,7 @@
     protected void onBindDialogView(View view) {
         super.onBindDialogView(view);
 
-        mEditText = (EditText) view.findViewById(android.R.id.edit);
+        mEditText = view.findViewById(android.R.id.edit);
 
         if (mEditText == null) {
             throw new IllegalStateException("Dialog view must contain an EditText with id" +
@@ -70,6 +70,8 @@
         }
 
         mEditText.setText(mText);
+        // Place cursor at the end
+        mEditText.setSelection(mText.length());
     }
 
     private EditTextPreference getEditTextPreference() {
diff --git a/preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java b/preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java
index 2e2960d..9a7cf66 100644
--- a/preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java
+++ b/preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java
@@ -22,8 +22,6 @@
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 
-import java.util.ArrayList;
-
 public class ListPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
 
     private static final String SAVE_STATE_INDEX = "ListPreferenceDialogFragment.index";
@@ -60,8 +58,8 @@
             mEntryValues = preference.getEntryValues();
         } else {
             mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
-            mEntries = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRIES);
-            mEntryValues = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRY_VALUES);
+            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
         }
     }
 
@@ -69,24 +67,8 @@
     public void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
-        putCharSequenceArray(outState, SAVE_STATE_ENTRIES, mEntries);
-        putCharSequenceArray(outState, SAVE_STATE_ENTRY_VALUES, mEntryValues);
-    }
-
-    private static void putCharSequenceArray(Bundle out, String key, CharSequence[] entries) {
-        final ArrayList<String> stored = new ArrayList<>(entries.length);
-
-        for (final CharSequence cs : entries) {
-            stored.add(cs.toString());
-        }
-
-        out.putStringArrayList(key, stored);
-    }
-
-    private static CharSequence[] getCharSequenceArray(Bundle in, String key) {
-        final ArrayList<String> stored = in.getStringArrayList(key);
-
-        return stored == null ? null : stored.toArray(new CharSequence[stored.size()]);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
     }
 
     private ListPreference getListPreference() {
diff --git a/room/common/api/1.0.0.txt b/room/common/api_legacy/1.0.0.txt
similarity index 100%
rename from room/common/api/1.0.0.txt
rename to room/common/api_legacy/1.0.0.txt
diff --git a/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java b/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
deleted file mode 100644
index 8d0b7ce..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/ColumnInfo.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.support.annotation.IntDef;
-import android.support.annotation.RequiresApi;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Allows specific customization about the column associated with this field.
- * <p>
- * For example, you can specify a column name for the field or change the column's type affinity.
- */
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.CLASS)
-public @interface ColumnInfo {
-    /**
-     * Name of the column in the database. Defaults to the field name if not set.
-     *
-     * @return Name of the column in the database.
-     */
-    String name() default INHERIT_FIELD_NAME;
-
-    /**
-     * The type affinity for the column, which will be used when constructing the database.
-     * <p>
-     * If it is not specified, the value defaults to {@link #UNDEFINED} and Room resolves it based
-     * on the field's type and available TypeConverters.
-     * <p>
-     * See <a href="https://www.sqlite.org/datatype3.html">SQLite types documentation</a> for
-     * details.
-     *
-     * @return The type affinity of the column. This is either {@link #UNDEFINED}, {@link #TEXT},
-     * {@link #INTEGER}, {@link #REAL}, or {@link #BLOB}.
-     */
-    @SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;
-
-    /**
-     * Convenience method to index the field.
-     * <p>
-     * If you would like to create a composite index instead, see: {@link Index}.
-     *
-     * @return True if this field should be indexed, false otherwise. Defaults to false.
-     */
-    boolean index() default false;
-
-    /**
-     * The collation sequence for the column, which will be used when constructing the database.
-     * <p>
-     * The default value is {@link #UNSPECIFIED}. In that case, Room does not add any
-     * collation sequence to the column, and SQLite treats it like {@link #BINARY}.
-     *
-     * @return The collation sequence of the column. This is either {@link #UNSPECIFIED},
-     * {@link #BINARY}, {@link #NOCASE}, {@link #RTRIM}, {@link #LOCALIZED} or {@link #UNICODE}.
-     */
-    @Collate int collate() default UNSPECIFIED;
-
-    /**
-     * Constant to let Room inherit the field name as the column name. If used, Room will use the
-     * field name as the column name.
-     */
-    String INHERIT_FIELD_NAME = "[field-name]";
-
-    /**
-     * Undefined type affinity. Will be resolved based on the type.
-     *
-     * @see #typeAffinity()
-     */
-    int UNDEFINED = 1;
-    /**
-     * Column affinity constant for strings.
-     *
-     * @see #typeAffinity()
-     */
-    int TEXT = 2;
-    /**
-     * Column affinity constant for integers or booleans.
-     *
-     * @see #typeAffinity()
-     */
-    int INTEGER = 3;
-    /**
-     * Column affinity constant for floats or doubles.
-     *
-     * @see #typeAffinity()
-     */
-    int REAL = 4;
-    /**
-     * Column affinity constant for binary data.
-     *
-     * @see #typeAffinity()
-     */
-    int BLOB = 5;
-
-    /**
-     * The SQLite column type constants that can be used in {@link #typeAffinity()}
-     */
-    @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
-    @interface SQLiteTypeAffinity {
-    }
-
-    /**
-     * Collation sequence is not specified. The match will behave like {@link #BINARY}.
-     *
-     * @see #collate()
-     */
-    int UNSPECIFIED = 1;
-    /**
-     * Collation sequence for case-sensitive match.
-     *
-     * @see #collate()
-     */
-    int BINARY = 2;
-    /**
-     * Collation sequence for case-insensitive match.
-     *
-     * @see #collate()
-     */
-    int NOCASE = 3;
-    /**
-     * Collation sequence for case-sensitive match except that trailing space characters are
-     * ignored.
-     *
-     * @see #collate()
-     */
-    int RTRIM = 4;
-    /**
-     * Collation sequence that uses system's current locale.
-     *
-     * @see #collate()
-     */
-    @RequiresApi(21)
-    int LOCALIZED = 5;
-    /**
-     * Collation sequence that uses Unicode Collation Algorithm.
-     *
-     * @see #collate()
-     */
-    @RequiresApi(21)
-    int UNICODE = 6;
-
-    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})
-    @interface Collate {
-    }
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Dao.java b/room/common/src/main/java/android/arch/persistence/room/Dao.java
deleted file mode 100644
index ea205e6..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Dao.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Marks the class as a Data Access Object.
- * <p>
- * Data Access Objects are the main classes where you define your database interactions. They can
- * include a variety of query methods.
- * <p>
- * The class marked with {@code @Dao} should either be an interface or an abstract class. At compile
- * time, Room will generate an implementation of this class when it is referenced by a
- * {@link Database}.
- * <p>
- * An abstract {@code @Dao} class can optionally have a constructor that takes a {@link Database}
- * as its only parameter.
- * <p>
- * It is recommended to have multiple {@code Dao} classes in your codebase depending on the tables
- * they touch.
- *
- * @see Query
- * @see Delete
- * @see Insert
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.CLASS)
-public @interface Dao {
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Database.java b/room/common/src/main/java/android/arch/persistence/room/Database.java
deleted file mode 100644
index 14e722f..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Database.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Marks a class as a RoomDatabase.
- * <p>
- * The class should be an abstract class and extend
- * {@link android.arch.persistence.room.RoomDatabase RoomDatabase}.
- * <p>
- * You can receive an implementation of the class via
- * {@link android.arch.persistence.room.Room#databaseBuilder Room.databaseBuilder} or
- * {@link android.arch.persistence.room.Room#inMemoryDatabaseBuilder Room.inMemoryDatabaseBuilder}.
- * <p>
- * <pre>
- * // User and Book are classes annotated with {@literal @}Entity.
- * {@literal @}Database(version = 1, entities = {User.class, Book.class})
- * abstract class AppDatabase extends RoomDatabase {
- *     // BookDao is a class annotated with {@literal @}Dao.
- *     abstract public BookDao bookDao();
- *     // UserDao is a class annotated with {@literal @}Dao.
- *     abstract public UserDao userDao();
- *     // UserBookDao is a class annotated with {@literal @}Dao.
- *     abstract public UserBookDao userBookDao();
- * }
- * </pre>
- * The example above defines a class that has 2 tables and 3 DAO classes that are used to access it.
- * There is no limit on the number of {@link Entity} or {@link Dao} classes but they must be unique
- * within the Database.
- * <p>
- * Instead of running queries on the database directly, you are highly recommended to create
- * {@link Dao} classes. Using Dao classes will allow you to abstract the database communication in
- * a more logical layer which will be much easier to mock in tests (compared to running direct
- * sql queries). It also automatically does the conversion from {@code Cursor} to your application
- * classes so you don't need to deal with lower level database APIs for most of your data access.
- * <p>
- * Room also verifies all of your queries in {@link Dao} classes while the application is being
- * compiled so that if there is a problem in one of the queries, you will be notified instantly.
- * @see Dao
- * @see Entity
- * @see android.arch.persistence.room.RoomDatabase RoomDatabase
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.CLASS)
-public @interface Database {
-    /**
-     * The list of entities included in the database. Each entity turns into a table in the
-     * database.
-     *
-     * @return The list of entities in the database.
-     */
-    Class[] entities();
-
-    /**
-     * The database version.
-     *
-     * @return The database version.
-     */
-    int version();
-
-    /**
-     * You can set annotation processor argument ({@code room.schemaLocation})
-     * to tell Room to export the schema into a folder. Even though it is not mandatory, it is a
-     * good practice to have version history in your codebase and you should commit that file into
-     * your version control system (but don't ship it with your app!).
-     * <p>
-     * When {@code room.schemaLocation} is set, Room will check this variable and if it is set to
-     * {@code true}, its schema will be exported into the given folder.
-     * <p>
-     * {@code exportSchema} is {@code true} by default but you can disable it for databases when
-     * you don't want to keep history of versions (like an in-memory only database).
-     *
-     * @return Whether the schema should be exported to the given folder when the
-     * {@code room.schemaLocation} argument is set. Defaults to {@code true}.
-     */
-    boolean exportSchema() default true;
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Delete.java b/room/common/src/main/java/android/arch/persistence/room/Delete.java
deleted file mode 100644
index 678b743..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Delete.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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 a {@link Dao} annotated class as a delete method.
- * <p>
- * The implementation of the method will delete its parameters from the database.
- * <p>
- * All of the parameters of the Delete method must either be classes annotated with {@link Entity}
- * or collections/array of it.
- * <p>
- * Example:
- * <pre>
- * {@literal @}Dao
- * public interface MyDao {
- *     {@literal @}Delete
- *     public void deleteUsers(User... users);
- *     {@literal @}Delete
- *     public void deleteAll(User user1, User user2);
- *     {@literal @}Delete
- *     public void deleteWithFriends(User user, List&lt;User&gt; friends);
- * }
- * </pre>
- *
- * @see Insert
- * @see Query
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.CLASS)
-public @interface Delete {
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Embedded.java b/room/common/src/main/java/android/arch/persistence/room/Embedded.java
deleted file mode 100644
index 781f026..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Embedded.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Can be used as an annotation on a field of an {@link Entity} or {@code Pojo} to signal that
- * nested fields (i.e. fields of the annotated field's class) can be referenced directly in the SQL
- * queries.
- * <p>
- * If the container is an {@link Entity}, these sub fields will be columns in the {@link Entity}'s
- * database table.
- * <p>
- * For example, if you have 2 classes:
- * <pre>
- *   public class Coordinates {
- *       double latitude;
- *       double longitude;
- *   }
- *   public class Address {
- *       String street;
- *       {@literal @}Embedded
- *       Coordinates coordinates;
- *   }
- * </pre>
- * Room will consider {@code latitude} and {@code longitude} as if they are fields of the
- * {@code Address} class when mapping an SQLite row to {@code Address}.
- * <p>
- * So if you have a query that returns {@code street, latitude, longitude}, Room will properly
- * construct an {@code Address} class.
- * <p>
- * If the {@code Address} class is annotated with {@link Entity}, its database table will have 3
- * columns: {@code street, latitude, longitude}
- * <p>
- * If there is a name conflict with the fields of the sub object and the owner object, you can
- * specify a {@link #prefix()} for the items of the sub object. Note that prefix is always applied
- * to sub fields even if they have a {@link ColumnInfo} with a specific {@code name}.
- * <p>
- * If sub fields of an embedded field has {@link PrimaryKey} annotation, they <b>will not</b> be
- * considered as primary keys in the owner {@link Entity}.
- * <p>
- * When an embedded field is read, if all fields of the embedded field (and its sub fields) are
- * {@code null} in the {@link android.database.Cursor Cursor}, it is set to {@code null}. Otherwise,
- * it is constructed.
- * <p>
- * Note that even if you have {@link TypeConverter}s that convert a {@code null} column into a
- * {@code non-null} value, if all columns of the embedded field in the
- * {@link android.database.Cursor Cursor} are null, the {@link TypeConverter} will never be called
- * and the embedded field will not be constructed.
- * <p>
- * You can override this behavior by annotating the embedded field with
- * {@link android.support.annotation.NonNull}.
- */
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.CLASS)
-public @interface Embedded {
-    /**
-     * Specifies a prefix to prepend the column names of the fields in the embedded fields.
-     * <p>
-     * For the example above, if we've written:
-     * <pre>
-     *   {@literal @}Embedded(prefix = "foo_")
-     *   Coordinates coordinates;
-     * </pre>
-     * The column names for {@code latitude} and {@code longitude} will be {@code foo_latitude} and
-     * {@code foo_longitude} respectively.
-     * <p>
-     * By default, prefix is the empty string.
-     *
-     * @return The prefix to be used for the fields of the embedded item.
-     */
-    String prefix() default  "";
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Entity.java b/room/common/src/main/java/android/arch/persistence/room/Entity.java
deleted file mode 100644
index 94ca3bf..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Entity.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Marks a class as an entity. This class will have a mapping SQLite table in the database.
- * <p>
- * Each entity must have at least 1 field annotated with {@link PrimaryKey}.
- * You can also use {@link #primaryKeys()} attribute to define the primary key.
- * <p>
- * Each entity must either have a no-arg constructor or a constructor whose parameters match
- * fields (based on type and name). Constructor does not have to receive all fields as parameters
- * but if a field is not passed into the constructor, it should either be public or have a public
- * setter. If a matching constructor is available, Room will always use it. If you don't want it
- * to use a constructor, you can annotate it with {@link Ignore}.
- * <p>
- * 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
- * public class User {
- *   {@literal @}PrimaryKey
- *   private final int uid;
- *   private String name;
- *   {@literal @}ColumnInfo(name = "last_name")
- *   private String lastName;
- *
- *   public User(int uid) {
- *       this.uid = uid;
- *   }
- *   public String getLastName() {
- *       return lastName;
- *   }
- *   public void setLastName(String lastName) {
- *       this.lastName = lastName;
- *   }
- * }
- * </pre>
- *
- * @see Dao
- * @see Database
- * @see PrimaryKey
- * @see ColumnInfo
- * @see Index
- */
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.CLASS)
-public @interface Entity {
-    /**
-     * The table name in the SQLite database. If not set, defaults to the class name.
-     *
-     * @return The SQLite tableName of the Entity.
-     */
-    String tableName() default "";
-
-    /**
-     * List of indices on the table.
-     *
-     * @return The list of indices on the table.
-     */
-    Index[] indices() default {};
-
-    /**
-     * If set to {@code true}, any Index defined in parent classes of this class will be carried
-     * over to the current {@code Entity}. Note that if you set this to {@code true}, even if the
-     * {@code Entity} has a parent which sets this value to {@code false}, the {@code Entity} will
-     * still inherit indices from it and its parents.
-     * <p>
-     * When the {@code Entity} inherits an index from the parent, it is <b>always</b> renamed with
-     * the default naming schema since SQLite <b>does not</b> allow using the same index name in
-     * multiple tables. See {@link Index} for the details of the default name.
-     * <p>
-     * By default, indices defined in parent classes are dropped to avoid unexpected indices.
-     * When this happens, you will receive a {@link RoomWarnings#INDEX_FROM_PARENT_FIELD_IS_DROPPED}
-     * or {@link RoomWarnings#INDEX_FROM_PARENT_IS_DROPPED} warning during compilation.
-     *
-     * @return True if indices from parent classes should be automatically inherited by this Entity,
-     *         false otherwise. Defaults to false.
-     */
-    boolean inheritSuperIndices() default false;
-
-    /**
-     * The list of Primary Key column names.
-     * <p>
-     * If you would like to define an auto generated primary key, you can use {@link PrimaryKey}
-     * annotation on the field with {@link PrimaryKey#autoGenerate()} set to {@code true}.
-     *
-     * @return The primary key of this Entity. Can be empty if the class has a field annotated
-     * with {@link PrimaryKey}.
-     */
-    String[] primaryKeys() default {};
-
-    /**
-     * List of {@link ForeignKey} constraints on this entity.
-     *
-     * @return The list of {@link ForeignKey} constraints on this entity.
-     */
-    ForeignKey[] foreignKeys() default {};
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/ForeignKey.java b/room/common/src/main/java/android/arch/persistence/room/ForeignKey.java
deleted file mode 100644
index 3ba632b..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/ForeignKey.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.support.annotation.IntDef;
-
-/**
- * Declares a foreign key on another {@link Entity}.
- * <p>
- * Foreign keys allows you to specify constraints across Entities such that SQLite will ensure that
- * the relationship is valid when you modify the database.
- * <p>
- * When a foreign key constraint is specified, SQLite requires the referenced columns to be part of
- * a unique index in the parent table or the primary key of that table. You must create a unique
- * index in the parent entity that covers the referenced columns (Room will verify this at compile
- * time and print an error if it is missing).
- * <p>
- * It is also recommended to create an index on the child table to avoid full table scans when the
- * parent table is modified. If a suitable index on the child table is missing, Room will print
- * {@link RoomWarnings#MISSING_INDEX_ON_FOREIGN_KEY_CHILD} warning.
- * <p>
- * A foreign key constraint can be deferred until the transaction is complete. This is useful if
- * you are doing bulk inserts into the database in a single transaction. By default, foreign key
- * constraints are immediate but you can change this value by setting {@link #deferred()} to
- * {@code true}. You can also use
- * <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>
- * documentation for details.
- */
-public @interface ForeignKey {
-    /**
-     * The parent Entity to reference. It must be a class annotated with {@link Entity} and
-     * referenced in the same database.
-     *
-     * @return The parent Entity.
-     */
-    Class entity();
-
-    /**
-     * The list of column names in the parent {@link Entity}.
-     * <p>
-     * Number of columns must match the number of columns specified in {@link #childColumns()}.
-     *
-     * @return The list of column names in the parent Entity.
-     * @see #childColumns()
-     */
-    String[] parentColumns();
-
-    /**
-     * The list of column names in the current {@link Entity}.
-     * <p>
-     * Number of columns must match the number of columns specified in {@link #parentColumns()}.
-     *
-     * @return The list of column names in the current Entity.
-     */
-    String[] childColumns();
-
-    /**
-     * Action to take when the parent {@link Entity} is deleted from the database.
-     * <p>
-     * By default, {@link #NO_ACTION} is used.
-     *
-     * @return The action to take when the referenced entity is deleted from the database.
-     */
-    @Action int onDelete() default NO_ACTION;
-
-    /**
-     * Action to take when the parent {@link Entity} is updated in the database.
-     * <p>
-     * By default, {@link #NO_ACTION} is used.
-     *
-     * @return The action to take when the referenced entity is updated in the database.
-     */
-    @Action int onUpdate() default NO_ACTION;
-
-    /**
-     * * A foreign key constraint can be deferred until the transaction is complete. This is useful
-     * if you are doing bulk inserts into the database in a single transaction. By default, foreign
-     * key constraints are immediate but you can change it by setting this field to {@code true}.
-     * You can also use
-     * <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a>
-     * PRAGMA to defer them depending on your transaction.
-     *
-     * @return Whether the foreign key constraint should be deferred until the transaction is
-     * complete. Defaults to {@code false}.
-     */
-    boolean deferred() default false;
-
-    /**
-     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
-     * <p>
-     * When a parent key is modified or deleted from the database, no special action is taken.
-     * This means that SQLite will not make any effort to fix the constraint failure, instead,
-     * reject the change.
-     */
-    int NO_ACTION = 1;
-
-    /**
-     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
-     * <p>
-     * The RESTRICT action means that the application is prohibited from deleting
-     * (for {@link #onDelete()}) or modifying (for {@link #onUpdate()}) a parent key when there
-     * exists one or more child keys mapped to it. The difference between the effect of a RESTRICT
-     * action and normal foreign key constraint enforcement is that the RESTRICT action processing
-     * happens as soon as the field is updated - not at the end of the current statement as it would
-     * with an immediate constraint, or at the end of the current transaction as it would with a
-     * {@link #deferred()} constraint.
-     * <p>
-     * Even if the foreign key constraint it is attached to is {@link #deferred()}, configuring a
-     * RESTRICT action causes SQLite to return an error immediately if a parent key with dependent
-     * child keys is deleted or modified.
-     */
-    int RESTRICT = 2;
-
-    /**
-     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
-     * <p>
-     * If the configured action is "SET NULL", then when a parent key is deleted
-     * (for {@link #onDelete()}) or modified (for {@link #onUpdate()}), the child key columns of all
-     * rows in the child table that mapped to the parent key are set to contain {@code NULL} values.
-     */
-    int SET_NULL = 3;
-
-    /**
-     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
-     * <p>
-     * The "SET DEFAULT" actions are similar to {@link #SET_NULL}, except that each of the child key
-     * columns is set to contain the columns default value instead of {@code NULL}.
-     */
-    int SET_DEFAULT = 4;
-
-    /**
-     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
-     * <p>
-     * A "CASCADE" action propagates the delete or update operation on the parent key to each
-     * dependent child key. For {@link #onDelete()} action, this means that each row in the child
-     * entity that was associated with the deleted parent row is also deleted. For an
-     * {@link #onUpdate()} action, it means that the values stored in each dependent child key are
-     * modified to match the new parent key values.
-     */
-    int CASCADE = 5;
-
-    /**
-     * Constants definition for values that can be used in {@link #onDelete()} and
-     * {@link #onUpdate()}.
-     */
-    @IntDef({NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT, CASCADE})
-    @interface Action {
-    }
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Ignore.java b/room/common/src/main/java/android/arch/persistence/room/Ignore.java
deleted file mode 100644
index 6effc33..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Ignore.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Ignores the marked element from Room's processing logic.
- * <p>
- * This annotation can be used in multiple places where Room processor runs. For instance, you can
- * add it to a field of an {@link Entity} and Room will not persist that field.
- */
-@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
-@Retention(RetentionPolicy.CLASS)
-public @interface Ignore {
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Index.java b/room/common/src/main/java/android/arch/persistence/room/Index.java
deleted file mode 100644
index b814efd..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Index.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Declares an index on an Entity.
- * see: <a href="https://sqlite.org/lang_createindex.html">SQLite Index Documentation</a>
- * <p>
- * Adding an index usually speeds up your select queries but will slow down other queries like
- * insert or update. You should be careful when adding indices to ensure that this additional cost
- * is worth the gain.
- * <p>
- * There are 2 ways to define an index in an {@link Entity}. You can either set
- * {@link ColumnInfo#index()} property to index individual fields or define composite indices via
- * {@link Entity#indices()}.
- * <p>
- * If an indexed field is embedded into another Entity via {@link Embedded}, it is <b>NOT</b>
- * added as an index to the containing {@link Entity}. If you want to keep it indexed, you must
- * re-declare it in the containing {@link Entity}.
- * <p>
- * Similarly, if an {@link Entity} extends another class, indices from the super classes are
- * <b>NOT</b> inherited. You must re-declare them in the child {@link Entity} or set
- * {@link Entity#inheritSuperIndices()} to {@code true}.
- * */
-@Target({})
-@Retention(RetentionPolicy.CLASS)
-public @interface Index {
-    /**
-     * List of column names in the Index.
-     * <p>
-     * The order of columns is important as it defines when SQLite can use a particular index.
-     * See <a href="https://www.sqlite.org/optoverview.html">SQLite documentation</a> for details on
-     * index usage in the query optimizer.
-     *
-     * @return The list of column names in the Index.
-     */
-    String[] value();
-
-    /**
-     * Name of the index. If not set, Room will set it to the list of columns joined by '_' and
-     * prefixed by "index_${tableName}". So if you have a table with name "Foo" and with an index
-     * of {"bar", "baz"}, generated index name will be  "index_Foo_bar_baz". If you need to specify
-     * the index in a query, you should never rely on this name, instead, specify a name for your
-     * index.
-     *
-     * @return The name of the index.
-     */
-    String name() default "";
-
-    /**
-     * If set to true, this will be a unique index and any duplicates will be rejected.
-     *
-     * @return True if index is unique. False by default.
-     */
-    boolean unique() default false;
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Insert.java b/room/common/src/main/java/android/arch/persistence/room/Insert.java
deleted file mode 100644
index 802dd96..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Insert.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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 a {@link Dao} annotated class as an insert method.
- * <p>
- * The implementation of the method will insert its parameters into the database.
- * <p>
- * All of the parameters of the Insert method must either be classes annotated with {@link Entity}
- * or collections/array of it.
- * <p>
- * Example:
- * <pre>
- * {@literal @}Dao
- * public interface MyDao {
- *     {@literal @}Insert(onConflict = OnConflictStrategy.REPLACE)
- *     public void insertUsers(User... users);
- *     {@literal @}Insert
- *     public void insertBoth(User user1, User user2);
- *     {@literal @}Insert
- *     public void insertWithFriends(User user, List&lt;User&gt; friends);
- * }
- * </pre>
- *
- * @see Update
- * @see Delete
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.CLASS)
-public @interface Insert {
-    /**
-     * What to do if a conflict happens.
-     * @see <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a>
-     *
-     * @return How to handle conflicts. Defaults to {@link OnConflictStrategy#ABORT}.
-     */
-    @OnConflictStrategy
-    int onConflict() default OnConflictStrategy.ABORT;
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/OnConflictStrategy.java b/room/common/src/main/java/android/arch/persistence/room/OnConflictStrategy.java
deleted file mode 100644
index 5217b61..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/OnConflictStrategy.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.support.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-
-/**
- * Set of conflict handling strategies for various {@link Dao} methods.
- * <p>
- * Check <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a> for
- * details.
- */
-@Retention(SOURCE)
-@IntDef({OnConflictStrategy.REPLACE, OnConflictStrategy.ROLLBACK, OnConflictStrategy.ABORT,
-        OnConflictStrategy.FAIL, OnConflictStrategy.IGNORE})
-public @interface OnConflictStrategy {
-    /**
-     * OnConflict strategy constant to replace the old data and continue the transaction.
-     */
-    int REPLACE = 1;
-    /**
-     * OnConflict strategy constant to rollback the transaction.
-     */
-    int ROLLBACK = 2;
-    /**
-     * OnConflict strategy constant to abort the transaction.
-     */
-    int ABORT = 3;
-    /**
-     * OnConflict strategy constant to fail the transaction.
-     */
-    int FAIL = 4;
-    /**
-     * OnConflict strategy constant to ignore the conflict.
-     */
-    int IGNORE = 5;
-
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/PrimaryKey.java b/room/common/src/main/java/android/arch/persistence/room/PrimaryKey.java
deleted file mode 100644
index 9a8062ca..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/PrimaryKey.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Marks a field in an {@link Entity} as the primary key.
- * <p>
- * If you would like to define a composite primary key, you should use {@link Entity#primaryKeys()}
- * method.
- * <p>
- * Each {@link Entity} must declare a primary key unless one of its super classes declares a
- * primary key. If both an {@link Entity} and its super class defines a {@code PrimaryKey}, the
- * child's {@code PrimaryKey} definition will override the parent's {@code PrimaryKey}.
- * <p>
- * If {@code PrimaryKey} annotation is used on a {@link Embedded}d field, all columns inherited
- * from that embedded field becomes the composite primary key (including its grand children
- * fields).
- */
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.CLASS)
-public @interface PrimaryKey {
-    /**
-     * Set to true to let SQLite generate the unique id.
-     * <p>
-     * When set to {@code true}, the SQLite type affinity for the field should be {@code INTEGER}.
-     * <p>
-     * If the field type is {@code long} or {@code int} (or its TypeConverter converts it to a
-     * {@code long} or {@code int}), {@link Insert} methods treat {@code 0} as not-set while
-     * inserting the item.
-     * <p>
-     * If the field's type is {@link Integer} or {@link Long} (or its TypeConverter converts it to
-     * an {@link Integer} or a {@link Long}), {@link Insert} methods treat {@code null} as
-     * not-set while inserting the item.
-     *
-     * @return Whether the primary key should be auto-generated by SQLite or not. Defaults
-     * to false.
-     */
-    boolean autoGenerate() default false;
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Query.java b/room/common/src/main/java/android/arch/persistence/room/Query.java
deleted file mode 100644
index bd838e8..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Query.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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 a {@link Dao} annotated class as a query method.
- * <p>
- * The value of the annotation includes the query that will be run when this method is called. This
- * query is <b>verified at compile time</b> by Room to ensure that it compiles fine against the
- * database.
- * <p>
- * The arguments of the method will be bound to the bind arguments in the SQL statement. See
- * <href="https://www.sqlite.org/c3ref/bind_blob.html">SQLite's binding documentation</> for
- * details of bind arguments in SQLite.
- * <p>
- * Room only supports named bind parameter {@code :name} to avoid any confusion between the
- * method parameters and the query bind parameters.
- * <p>
- * Room will automatically bind the parameters of the method into the bind arguments. This is done
- * by matching the name of the parameters to the name of the bind arguments.
- * <pre>
- *     {@literal @}Query("SELECT * FROM user WHERE user_name LIKE :name AND last_name LIKE :last")
- *     public abstract List&lt;User&gt; findUsersByNameAndLastName(String name, String last);
- * </pre>
- * <p>
- * As an extension over SQLite bind arguments, Room supports binding a list of parameters to the
- * query. At runtime, Room will build the correct query to have matching number of bind arguments
- * depending on the number of items in the method parameter.
- * <pre>
- *     {@literal @}Query("SELECT * FROM user WHERE uid IN(:userIds)")
- *     public abstract List<User> findByIds(int[] userIds);
- * </pre>
- * For the example above, if the {@code userIds} is an array of 3 elements, Room will run the
- * query as: {@code SELECT * FROM user WHERE uid IN(?, ?, ?)} and bind each item in the
- * {@code userIds} array into the statement.
- * <p>
- * There are 3 types of queries supported in {@code Query} methods: SELECT, UPDATE and DELETE.
- * <p>
- * For SELECT queries, Room will infer the result contents from the method's return type and
- * generate the code that will automatically convert the query result into the method's return
- * type. For single result queries, the return type can be any java object. For queries that return
- * multiple values, you can use {@link java.util.List} or {@code Array}. In addition to these, any
- * query may return {@link android.database.Cursor Cursor} or any query result can be wrapped in
- * a {@link android.arch.lifecycle.LiveData LiveData}.
- * <p>
- * <b>RxJava2</b> If you are using RxJava2, you can also return {@code Flowable<T>} or
- * {@code Publisher<T>} from query methods. Since Reactive Streams does not allow {@code null}, if
- * the query returns a nullable type, it will not dispatch anything if the value is {@code null}
- * (like fetching an {@link Entity} row that does not exist).
- * You can return {@code Flowable<T[]>} or {@code Flowable<List<T>>} to workaround this limitation.
- * <p>
- * Both {@code Flowable<T>} and {@code Publisher<T>} will observe the database for changes and
- * re-dispatch if data changes. If you want to query the database without observing changes, you can
- * use {@code Maybe<T>} or {@code Single<T>}. If a {@code Single<T>} query returns {@code null},
- * Room will throw
- * {@link android.arch.persistence.room.EmptyResultSetException EmptyResultSetException}.
- * <p>
- * UPDATE or DELETE queries can return {@code void} or {@code int}. If it is an {@code int},
- * the value is the number of rows affected by this query.
- * <p>
- * You can return arbitrary POJOs from your query methods as long as the fields of the POJO match
- * the column names in the query result.
- * For example, if you have class:
- * <pre>
- * class UserName {
- *     public String name;
- *     {@literal @}ColumnInfo(name = "last_name")
- *     public String lastName;
- * }
- * </pre>
- * You can write a query like this:
- * <pre>
- *     {@literal @}Query("SELECT last_name, name FROM user WHERE uid = :userId LIMIT 1")
- *     public abstract UserName findOneUserName(int userId);
- * </pre>
- * And Room will create the correct implementation to convert the query result into a
- * {@code UserName} object. If there is a mismatch between the query result and the fields of the
- * POJO, as long as there is at least 1 field match, Room prints a
- * {@link RoomWarnings#CURSOR_MISMATCH} warning and sets as many fields as it can.
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.CLASS)
-public @interface Query {
-    /**
-     * The SQLite query to be run.
-     * @return The query to be run.
-     */
-    String value();
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/RawQuery.java b/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
deleted file mode 100644
index 46f2193..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/RawQuery.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright 2018 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 a {@link Dao} annotated class as a raw query method where you can pass the
- * query as a {@link String} or a
- * {@link android.arch.persistence.db.SupportSQLiteQuery SupportSQLiteQuery}.
- * <pre>
- * {@literal @}Dao
- * interface RawDao {
- *     {@literal @}RawQuery
- *     User getUser(String query);
- *     {@literal @}RawQuery
- *     User getUserViaQuery(SupportSQLiteQuery query);
- * }
- * User user = rawDao.getUser("SELECT * FROM User WHERE id = 3 LIMIT 1");
- * SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM User WHERE id = ? LIMIT 1",
- *         new Object[]{3});
- * User user2 = rawDao.getUserViaQuery(query);
- * </pre>
- * <p>
- * Room will generate the code based on the return type of the function and failure to
- * pass a proper query will result in a runtime failure or an undefined result.
- * <p>
- * If you know the query at compile time, you should always prefer {@link Query} since it validates
- * the query at compile time and also generates more efficient code since Room can compute the
- * query result at compile time (e.g. it does not need to account for possibly missing columns in
- * the response).
- * <p>
- * On the other hand, {@code RawQuery} serves as an escape hatch where you can build your own
- * SQL query at runtime but still use Room to convert it into objects.
- * <p>
- * {@code RawQuery} methods must return a non-void type. If you want to execute a raw query that
- * does not return any value, use {@link android.arch.persistence.room.RoomDatabase#query
- * RoomDatabase#query} methods.
- * <p>
- * <b>Observable Queries:</b>
- * <p>
- * {@code RawQuery} methods can return observable types but you need to specify which tables are
- * accessed in the query using the {@link #observedEntities()} field in the annotation.
- * <pre>
- * {@literal @}Dao
- * interface RawDao {
- *     {@literal @}RawQuery(observedEntities = User.class)
- *     LiveData&lt;List&lt;User>> getUsers(String query);
- * }
- * LiveData&lt;List&lt;User>> liveUsers = rawDao.getUsers("SELECT * FROM User ORDER BY name DESC");
- * </pre>
- * <b>Returning Pojos:</b>
- * <p>
- * RawQueries can also return plain old java objects, similar to {@link Query} methods.
- * <pre>
- * public class NameAndLastName {
- *     public final String name;
- *     public final String lastName;
- *
- *     public NameAndLastName(String name, String lastName) {
- *         this.name = name;
- *         this.lastName = lastName;
- *     }
- * }
- *
- * {@literal @}Dao
- * interface RawDao {
- *     {@literal @}RawQuery
- *     NameAndLastName getNameAndLastName(String query);
- * }
- * NameAndLastName result = rawDao.getNameAndLastName("SELECT * FROM User WHERE id = 3")
- * // or
- * NameAndLastName result = rawDao.getNameAndLastName("SELECT name, lastName FROM User WHERE id =
- * 3")
- * </pre>
- * <p>
- * <b>Pojos with Embedded Fields:</b>
- * <p>
- * {@code RawQuery} methods can return pojos that include {@link Embedded} fields as well.
- * <pre>
- * public class UserAndPet {
- *     {@literal @}Embedded
- *     public User user;
- *     {@literal @}Embedded
- *     public Pet pet;
- * }
- *
- * {@literal @}Dao
- * interface RawDao {
- *     {@literal @}RawQuery
- *     UserAndPet getUserAndPet(String query);
- * }
- * UserAndPet received = rawDao.getUserAndPet(
- *         "SELECT * FROM User, Pet WHERE User.id = Pet.userId LIMIT 1")
- * </pre>
- *
- * <b>Relations:</b>
- * <p>
- * {@code RawQuery} return types can also be objects with {@link Relation Relations}.
- * <pre>
- * public class UserAndAllPets {
- *     {@literal @}Embedded
- *     public User user;
- *     {@literal @}Relation(parentColumn = "id", entityColumn = "userId")
- *     public List&lt;Pet> pets;
- * }
- *
- * {@literal @}Dao
- * interface RawDao {
- *     {@literal @}RawQuery
- *     List&lt;UserAndAllPets> getUsersAndAllPets(String query);
- * }
- * List&lt;UserAndAllPets> result = rawDao.getUsersAndAllPets("SELECT * FROM users");
- * </pre>
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.CLASS)
-public @interface RawQuery {
-    /**
-     * Denotes the list of entities which are accessed in the provided query and should be observed
-     * for invalidation if the query is observable.
-     * <p>
-     * The listed classes should either be annotated with {@link Entity} or they should reference to
-     * at least 1 Entity (via {@link Embedded} or {@link Relation}).
-     * <p>
-     * Providing this field in a non-observable query has no impact.
-     * <pre>
-     * {@literal @}Dao
-     * interface RawDao {
-     *     {@literal @}RawQuery(observedEntities = User.class)
-     *     LiveData&lt;List&lt;User>> getUsers(String query);
-     * }
-     * LiveData&lt;List&lt;User>> liveUsers = rawDao.getUsers("select * from User ORDER BY name
-     * DESC");
-     * </pre>
-     *
-     * @return List of entities that should invalidate the query if changed.
-     */
-    Class[] observedEntities() default {};
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Relation.java b/room/common/src/main/java/android/arch/persistence/room/Relation.java
deleted file mode 100644
index d55bbfe..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Relation.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * A convenience annotation which can be used in a Pojo to automatically fetch relation entities.
- * When the Pojo is returned from a query, all of its relations are also fetched by Room.
- *
- * <pre>
- * {@literal @}Entity
- * public class Pet {
- *     {@literal @} PrimaryKey
- *     int id;
- *     int userId;
- *     String name;
- *     // other fields
- * }
- * public class UserNameAndAllPets {
- *   public int id;
- *   public String name;
- *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId")
- *   public List&lt;Pet&gt; pets;
- * }
- *
- * {@literal @}Dao
- * public interface UserPetDao {
- *     {@literal @}Query("SELECT id, name from User")
- *     public List&lt;UserNameAndAllPets&gt; loadUserAndPets();
- * }
- * </pre>
- * <p>
- * The type of the field annotated with {@code Relation} must be a {@link java.util.List} or
- * {@link java.util.Set}. By default, the {@link Entity} type is inferred from the return type.
- * If you would like to return a different object, you can specify the {@link #entity()} property
- * in the annotation.
- * <pre>
- * public class User {
- *     int id;
- *     // other fields
- * }
- * public class PetNameAndId {
- *     int id;
- *     String name;
- * }
- * public class UserAllPets {
- *   {@literal @}Embedded
- *   public User user;
- *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
- *   public List&lt;PetNameAndId&gt; pets;
- * }
- * {@literal @}Dao
- * public interface UserPetDao {
- *     {@literal @}Query("SELECT * from User")
- *     public List&lt;UserAllPets&gt; loadUserAndPets();
- * }
- * </pre>
- * <p>
- * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched
- * from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>).
- * {@code PetNameAndId} could also define its own relations all of which would also be fetched
- * automatically.
- * <p>
- * If you would like to specify which columns are fetched from the child {@link Entity}, you can
- * use {@link #projection()} property in the {@code Relation} annotation.
- * <pre>
- * public class UserAndAllPets {
- *   {@literal @}Embedded
- *   public User user;
- *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class,
- *           projection = {"name"})
- *   public List&lt;String&gt; petNames;
- * }
- * </pre>
- * <p>
- * Note that {@code @Relation} annotation can be used only in Pojo classes, an {@link Entity} class
- * cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity}
- * setups. You can read more about it in the main Room documentation. When loading data, you can
- * simply work around this limitation by creating Pojo classes that extend the {@link Entity}.
- * <p>
- * Note that the {@code @Relation} annotated field cannot be a constructor parameter, it must be
- * public or have a public setter.
- */
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.CLASS)
-public @interface Relation {
-    /**
-     * The entity to fetch the item from. You don't need to set this if the entity matches the
-     * type argument in the return type.
-     *
-     * @return The entity to fetch from. By default, inherited from the return type.
-     */
-    Class entity() default Object.class;
-
-    /**
-     * Reference field in the parent Pojo.
-     * <p>
-     * If you would like to access to a sub item of a {@link Embedded}d field, you can use
-     * the {@code .} notation.
-     * <p>
-     * For instance, if you have a {@link Embedded}d field named {@code user} with a sub field
-     * {@code id}, you can reference it via {@code user.id}.
-     * <p>
-     * This value will be matched against the value defined in {@link #entityColumn()}.
-     *
-     * @return The field reference in the parent object.
-     */
-    String parentColumn();
-
-    /**
-     * The field path to match in the {@link #entity()}. This value will be matched against the
-     * value defined in {@link #parentColumn()}.
-     */
-    String entityColumn();
-
-    /**
-     * If sub fields should be fetched from the entity, you can specify them using this field.
-     * <p>
-     * By default, inferred from the the return type.
-     *
-     * @return The list of columns to be selected from the {@link #entity()}.
-     */
-    String[] projection() default {};
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/RoomMasterTable.java b/room/common/src/main/java/android/arch/persistence/room/RoomMasterTable.java
deleted file mode 100644
index 621845d..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/RoomMasterTable.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.support.annotation.RestrictTo;
-
-/**
- * Schema information about Room's master table.
- *
- * @hide
- */
-@SuppressWarnings("WeakerAccess")
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class RoomMasterTable {
-    /**
-     * The master table where room keeps its metadata information.
-     */
-    public static final String TABLE_NAME = "room_master_table";
-    // must match the runtime property Room#MASTER_TABLE_NAME
-    public static final String NAME = "room_master_table";
-    private static final String COLUMN_ID = "id";
-    private static final String COLUMN_IDENTITY_HASH = "identity_hash";
-    public static final String DEFAULT_ID = "42";
-
-    public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
-            + COLUMN_ID + " INTEGER PRIMARY KEY,"
-            + COLUMN_IDENTITY_HASH + " TEXT)";
-
-    public static final String READ_QUERY = "SELECT " + COLUMN_IDENTITY_HASH
-            + " FROM " + TABLE_NAME + " WHERE "
-            + COLUMN_ID + " = " + DEFAULT_ID + " LIMIT 1";
-
-    /**
-     * We don't escape here since we know what we are passing.
-     */
-    public static String createInsertQuery(String hash) {
-        return "INSERT OR REPLACE INTO " + TABLE_NAME + " ("
-                + COLUMN_ID + "," + COLUMN_IDENTITY_HASH + ")"
-                + " VALUES(" + DEFAULT_ID + ", \"" + hash + "\")";
-    }
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java b/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java
deleted file mode 100644
index f05e6be..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/RoomWarnings.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-/**
- * The list of warnings that are produced by Room.
- * <p>
- * You can use these values inside a {@link SuppressWarnings} annotation to disable the warnings.
- */
-@SuppressWarnings({"unused", "WeakerAccess"})
-public class RoomWarnings {
-    /**
-     * The warning dispatched by Room when the return value of a {@link Query} method does not
-     * exactly match the fields in the query result.
-     */
-    // if you change this, don't forget to change android.arch.persistence.room.vo.Warning
-    public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
-
-    /**
-     * Reported when Room cannot verify database queries during compilation due to lack of
-     * tmp dir access in JVM.
-     */
-    public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
-
-    /**
-     * Reported when Room cannot verify database queries during compilation. This usually happens
-     * when it cannot find the SQLite JDBC driver on the host machine.
-     * <p>
-     * Room can function without query verification but its functionality will be limited.
-     */
-    public static final String CANNOT_CREATE_VERIFICATION_DATABASE =
-            "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
-
-    /**
-     * Reported when an {@link Entity} field that is annotated with {@link Embedded} has a
-     * sub field which is annotated with {@link PrimaryKey} but the {@link PrimaryKey} is dropped
-     * while composing it into the parent object.
-     */
-    public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED =
-            "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
-
-    /**
-     * Reported when an {@link Entity} field that is annotated with {@link Embedded} has a
-     * sub field which has a {@link ColumnInfo} annotation with {@code index = true}.
-     * <p>
-     * You can re-define the index in the containing {@link Entity}.
-     */
-    public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED =
-            "ROOM_EMBEDDED_INDEX_IS_DROPPED";
-
-    /**
-     * Reported when an {@link Entity} that has a {@link Embedded}d field whose type is another
-     * {@link Entity} and that {@link Entity} has some indices defined.
-     * These indices will NOT be created in the containing {@link Entity}. If you want to preserve
-     * them, you can re-define them in the containing {@link Entity}.
-     */
-    public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED =
-            "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
-
-    /**
-     * Reported when an {@link Entity}'s parent declares an {@link Index}. Room does not
-     * automatically inherit these indices to avoid hidden costs or unexpected constraints.
-     * <p>
-     * If you want your child class to have the indices of the parent, you must re-declare
-     * them in the child class. Alternatively, you can set {@link Entity#inheritSuperIndices()}
-     * to {@code true}.
-     */
-    public static final String INDEX_FROM_PARENT_IS_DROPPED =
-            "ROOM_PARENT_INDEX_IS_DROPPED";
-
-    /**
-     * Reported when an {@link Entity} inherits a field from its super class and the field has a
-     * {@link ColumnInfo} annotation with {@code index = true}.
-     * <p>
-     * These indices are dropped for the {@link Entity} and you would need to re-declare them if
-     * you want to keep them. Alternatively, you can set {@link Entity#inheritSuperIndices()}
-     * to {@code true}.
-     */
-    public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED =
-            "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
-
-    /**
-     * Reported when a {@link Relation} {@link Entity}'s SQLite column type does not match the type
-     * in the parent. Room will still do the matching using {@code String} representations.
-     */
-    public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
-
-    /**
-     * Reported when a `room.schemaLocation` argument is not provided into the annotation processor.
-     * You can either set {@link Database#exportSchema()} to {@code false} or provide
-     * `room.schemaLocation` to the annotation processor. You are strongly adviced to provide it
-     * and also commit them into your version control system.
-     */
-    public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
-
-    /**
-     * When there is a foreign key from Entity A to Entity B, it is a good idea to index the
-     * reference columns in B, otherwise, each modification on Entity A will trigger a full table
-     * scan on Entity B.
-     * <p>
-     * If Room cannot find a proper index in the child entity (Entity B in this case), Room will
-     * print this warning.
-     */
-    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";
-
-    /**
-     * Reported when a @Query method returns a Pojo that has relations but the method is not
-     * annotated with @Transaction. Relations are run as separate queries and if the query is not
-     * run inside a transaction, it might return inconsistent results from the database.
-     */
-    public static final String RELATION_QUERY_WITHOUT_TRANSACTION =
-            "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/SkipQueryVerification.java b/room/common/src/main/java/android/arch/persistence/room/SkipQueryVerification.java
deleted file mode 100644
index dc2ded3..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/SkipQueryVerification.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Skips database verification for the annotated element.
- * <p>
- * If it is a class annotated with {@link Database}, none of the queries for the database will
- * be verified at compile time.
- * <p>
- * If it is a class annotated with {@link Dao}, none of the queries in the Dao class will
- * be verified at compile time.
- * <p>
- * If it is a method in a Dao class, just the method's sql verification will be skipped.
- * <p>
- * You should use this as the last resort if Room cannot properly understand your query and you are
- * 100% sure it works. Removing validation may limit the functionality of Room since it won't be
- * able to understand the query response.
- */
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.CLASS)
-public @interface SkipQueryVerification {
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Transaction.java b/room/common/src/main/java/android/arch/persistence/room/Transaction.java
deleted file mode 100644
index 3b6ede9..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Transaction.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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 a {@link Dao} class as a transaction method.
- * <p>
- * When used on a non-abstract method of an abstract {@link Dao} class,
- * 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>
- * <p>
- * When used on a {@link Query} method that has a {@code Select} statement, the generated code for
- * the Query will be run in a transaction. There are 2 main cases where you may want to do that:
- * <ol>
- *     <li>If the result of the query is fairly big, it is better to run it inside a transaction
- *     to receive a consistent result. Otherwise, if the query result does not fit into a single
- *     {@link android.database.CursorWindow CursorWindow}, the query result may be corrupted due to
- *     changes in the database in between cursor window swaps.
- *     <li>If the result of the query is a Pojo with {@link Relation} fields, these fields are
- *     queried separately. To receive consistent results between these queries, you probably want
- *     to run them in a single transaction.
- * </ol>
- * Example:
- * <pre>
- * class ProductWithReviews extends Product {
- *     {@literal @}Relation(parentColumn = "id", entityColumn = "productId", entity = Review.class)
- *     public List&lt;Review> reviews;
- * }
- * {@literal @}Dao
- * public interface ProductDao {
- *     {@literal @}Transaction {@literal @}Query("SELECT * from products")
- *     public List&lt;ProductWithReviews> loadAll();
- * }
- * </pre>
- * If the query is an async query (e.g. returns a {@link android.arch.lifecycle.LiveData LiveData}
- * or RxJava Flowable, the transaction is properly handled when the query is run, not when the
- * method is called.
- * <p>
- * Putting this annotation on an {@link Insert}, {@link Update} or {@link Delete} method has no
- * impact because they are always run inside a transaction. Similarly, if it is annotated with
- * {@link Query} but runs an update or delete statement, it is automatically wrapped in a
- * transaction.
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.CLASS)
-public @interface Transaction {
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/TypeConverter.java b/room/common/src/main/java/android/arch/persistence/room/TypeConverter.java
deleted file mode 100644
index 850481c..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/TypeConverter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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 as a type converter. A class can have as many @TypeConverter methods as it needs.
- * <p>
- * Each converter method should receive 1 parameter and have non-void return type.
- *
- * <pre>
- * // example converter for java.util.Date
- * public static class Converters {
- *    {@literal @}TypeConverter
- *    public Date fromTimestamp(Long value) {
- *        return value == null ? null : new Date(value);
- *    }
- *
- *    {@literal @}TypeConverter
- *    public Long dateToTimestamp(Date date) {
- *        if (date == null) {
- *            return null;
- *        } else {
- *            return date.getTime();
- *        }
- *    }
- *}
- * </pre>
- * @see TypeConverters
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.CLASS)
-public @interface TypeConverter {
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/TypeConverters.java b/room/common/src/main/java/android/arch/persistence/room/TypeConverters.java
deleted file mode 100644
index 7f7e6ab..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/TypeConverters.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Specifies additional type converters that Room can use. The TypeConverter is added to the scope
- * of the element so if you put it on a class / interface, all methods / fields in that class will
- * be able to use the converters.
- * <ul>
- * <li>If you put it on a {@link Database}, all Daos and Entities in that database will be able to
- * use it.
- * <li>If you put it on a {@link Dao}, all methods in the Dao will be able to use it.
- * <li>If you put it on an {@link Entity}, all fields of the Entity will be able to use it.
- * <li>If you put it on a POJO, all fields of the POJO will be able to use it.
- * <li>If you put it on an {@link Entity} field, only that field will be able to use it.
- * <li>If you put it on a {@link Dao} method, all parameters of the method will be able to use it.
- * <li>If you put it on a {@link Dao} method parameter, just that field will be able to use it.
- * </ul>
- * @see TypeConverter
- */
-@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
-@Retention(RetentionPolicy.CLASS)
-public @interface TypeConverters {
-    /**
-     * The list of type converter classes. If converter methods are not static, Room will create
-     * an instance of these classes.
-     *
-     * @return The list of classes that contains the converter methods.
-     */
-    Class<?>[] value();
-}
diff --git a/room/common/src/main/java/android/arch/persistence/room/Update.java b/room/common/src/main/java/android/arch/persistence/room/Update.java
deleted file mode 100644
index 670a085..0000000
--- a/room/common/src/main/java/android/arch/persistence/room/Update.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room;
-
-/**
- * Marks a method in a {@link Dao} annotated class as an update method.
- * <p>
- * The implementation of the method will update its parameters in the database if they already
- * exists (checked by primary keys). If they don't already exists, this option will not change the
- * database.
- * <p>
- * All of the parameters of the Update method must either be classes annotated with {@link Entity}
- * or collections/array of it.
- *
- * @see Insert
- * @see Delete
- */
-public @interface Update {
-    /**
-     * What to do if a conflict happens.
-     * @see <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a>
-     *
-     * @return How to handle conflicts. Defaults to {@link OnConflictStrategy#ABORT}.
-     */
-    @OnConflictStrategy
-    int onConflict() default OnConflictStrategy.ABORT;
-}
diff --git a/room/common/src/main/java/androidx/room/ColumnInfo.java b/room/common/src/main/java/androidx/room/ColumnInfo.java
new file mode 100644
index 0000000..404f334
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/ColumnInfo.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RequiresApi;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Allows specific customization about the column associated with this field.
+ * <p>
+ * For example, you can specify a column name for the field or change the column's type affinity.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface ColumnInfo {
+    /**
+     * Name of the column in the database. Defaults to the field name if not set.
+     *
+     * @return Name of the column in the database.
+     */
+    String name() default INHERIT_FIELD_NAME;
+
+    /**
+     * The type affinity for the column, which will be used when constructing the database.
+     * <p>
+     * If it is not specified, the value defaults to {@link #UNDEFINED} and Room resolves it based
+     * on the field's type and available TypeConverters.
+     * <p>
+     * See <a href="https://www.sqlite.org/datatype3.html">SQLite types documentation</a> for
+     * details.
+     *
+     * @return The type affinity of the column. This is either {@link #UNDEFINED}, {@link #TEXT},
+     * {@link #INTEGER}, {@link #REAL}, or {@link #BLOB}.
+     */
+    @SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;
+
+    /**
+     * Convenience method to index the field.
+     * <p>
+     * If you would like to create a composite index instead, see: {@link Index}.
+     *
+     * @return True if this field should be indexed, false otherwise. Defaults to false.
+     */
+    boolean index() default false;
+
+    /**
+     * The collation sequence for the column, which will be used when constructing the database.
+     * <p>
+     * The default value is {@link #UNSPECIFIED}. In that case, Room does not add any
+     * collation sequence to the column, and SQLite treats it like {@link #BINARY}.
+     *
+     * @return The collation sequence of the column. This is either {@link #UNSPECIFIED},
+     * {@link #BINARY}, {@link #NOCASE}, {@link #RTRIM}, {@link #LOCALIZED} or {@link #UNICODE}.
+     */
+    @Collate int collate() default UNSPECIFIED;
+
+    /**
+     * Constant to let Room inherit the field name as the column name. If used, Room will use the
+     * field name as the column name.
+     */
+    String INHERIT_FIELD_NAME = "[field-name]";
+
+    /**
+     * Undefined type affinity. Will be resolved based on the type.
+     *
+     * @see #typeAffinity()
+     */
+    int UNDEFINED = 1;
+    /**
+     * Column affinity constant for strings.
+     *
+     * @see #typeAffinity()
+     */
+    int TEXT = 2;
+    /**
+     * Column affinity constant for integers or booleans.
+     *
+     * @see #typeAffinity()
+     */
+    int INTEGER = 3;
+    /**
+     * Column affinity constant for floats or doubles.
+     *
+     * @see #typeAffinity()
+     */
+    int REAL = 4;
+    /**
+     * Column affinity constant for binary data.
+     *
+     * @see #typeAffinity()
+     */
+    int BLOB = 5;
+
+    /**
+     * The SQLite column type constants that can be used in {@link #typeAffinity()}
+     */
+    @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
+    @interface SQLiteTypeAffinity {
+    }
+
+    /**
+     * Collation sequence is not specified. The match will behave like {@link #BINARY}.
+     *
+     * @see #collate()
+     */
+    int UNSPECIFIED = 1;
+    /**
+     * Collation sequence for case-sensitive match.
+     *
+     * @see #collate()
+     */
+    int BINARY = 2;
+    /**
+     * Collation sequence for case-insensitive match.
+     *
+     * @see #collate()
+     */
+    int NOCASE = 3;
+    /**
+     * Collation sequence for case-sensitive match except that trailing space characters are
+     * ignored.
+     *
+     * @see #collate()
+     */
+    int RTRIM = 4;
+    /**
+     * Collation sequence that uses system's current locale.
+     *
+     * @see #collate()
+     */
+    @RequiresApi(21)
+    int LOCALIZED = 5;
+    /**
+     * Collation sequence that uses Unicode Collation Algorithm.
+     *
+     * @see #collate()
+     */
+    @RequiresApi(21)
+    int UNICODE = 6;
+
+    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})
+    @interface Collate {
+    }
+}
diff --git a/room/common/src/main/java/androidx/room/Dao.java b/room/common/src/main/java/androidx/room/Dao.java
new file mode 100644
index 0000000..c62c641
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Dao.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks the class as a Data Access Object.
+ * <p>
+ * Data Access Objects are the main classes where you define your database interactions. They can
+ * include a variety of query methods.
+ * <p>
+ * The class marked with {@code @Dao} should either be an interface or an abstract class. At compile
+ * time, Room will generate an implementation of this class when it is referenced by a
+ * {@link Database}.
+ * <p>
+ * An abstract {@code @Dao} class can optionally have a constructor that takes a {@link Database}
+ * as its only parameter.
+ * <p>
+ * It is recommended to have multiple {@code Dao} classes in your codebase depending on the tables
+ * they touch.
+ *
+ * @see Query
+ * @see Delete
+ * @see Insert
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface Dao {
+}
diff --git a/room/common/src/main/java/androidx/room/Database.java b/room/common/src/main/java/androidx/room/Database.java
new file mode 100644
index 0000000..40058cd
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Database.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as a RoomDatabase.
+ * <p>
+ * The class should be an abstract class and extend
+ * {@link androidx.room.RoomDatabase RoomDatabase}.
+ * <p>
+ * You can receive an implementation of the class via
+ * {@link androidx.room.Room#databaseBuilder Room.databaseBuilder} or
+ * {@link androidx.room.Room#inMemoryDatabaseBuilder Room.inMemoryDatabaseBuilder}.
+ * <p>
+ * <pre>
+ * // User and Book are classes annotated with {@literal @}Entity.
+ * {@literal @}Database(version = 1, entities = {User.class, Book.class})
+ * abstract class AppDatabase extends RoomDatabase {
+ *     // BookDao is a class annotated with {@literal @}Dao.
+ *     abstract public BookDao bookDao();
+ *     // UserDao is a class annotated with {@literal @}Dao.
+ *     abstract public UserDao userDao();
+ *     // UserBookDao is a class annotated with {@literal @}Dao.
+ *     abstract public UserBookDao userBookDao();
+ * }
+ * </pre>
+ * The example above defines a class that has 2 tables and 3 DAO classes that are used to access it.
+ * There is no limit on the number of {@link Entity} or {@link Dao} classes but they must be unique
+ * within the Database.
+ * <p>
+ * Instead of running queries on the database directly, you are highly recommended to create
+ * {@link Dao} classes. Using Dao classes will allow you to abstract the database communication in
+ * a more logical layer which will be much easier to mock in tests (compared to running direct
+ * sql queries). It also automatically does the conversion from {@code Cursor} to your application
+ * classes so you don't need to deal with lower level database APIs for most of your data access.
+ * <p>
+ * Room also verifies all of your queries in {@link Dao} classes while the application is being
+ * compiled so that if there is a problem in one of the queries, you will be notified instantly.
+ * @see Dao
+ * @see Entity
+ * @see androidx.room.RoomDatabase RoomDatabase
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface Database {
+    /**
+     * The list of entities included in the database. Each entity turns into a table in the
+     * database.
+     *
+     * @return The list of entities in the database.
+     */
+    Class[] entities();
+
+    /**
+     * The database version.
+     *
+     * @return The database version.
+     */
+    int version();
+
+    /**
+     * You can set annotation processor argument ({@code room.schemaLocation})
+     * to tell Room to export the schema into a folder. Even though it is not mandatory, it is a
+     * good practice to have version history in your codebase and you should commit that file into
+     * your version control system (but don't ship it with your app!).
+     * <p>
+     * When {@code room.schemaLocation} is set, Room will check this variable and if it is set to
+     * {@code true}, its schema will be exported into the given folder.
+     * <p>
+     * {@code exportSchema} is {@code true} by default but you can disable it for databases when
+     * you don't want to keep history of versions (like an in-memory only database).
+     *
+     * @return Whether the schema should be exported to the given folder when the
+     * {@code room.schemaLocation} argument is set. Defaults to {@code true}.
+     */
+    boolean exportSchema() default true;
+}
diff --git a/room/common/src/main/java/androidx/room/Delete.java b/room/common/src/main/java/androidx/room/Delete.java
new file mode 100644
index 0000000..7ca63ad
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Delete.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.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 a {@link Dao} annotated class as a delete method.
+ * <p>
+ * The implementation of the method will delete its parameters from the database.
+ * <p>
+ * All of the parameters of the Delete method must either be classes annotated with {@link Entity}
+ * or collections/array of it.
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Dao
+ * public interface MyDao {
+ *     {@literal @}Delete
+ *     public void deleteUsers(User... users);
+ *     {@literal @}Delete
+ *     public void deleteAll(User user1, User user2);
+ *     {@literal @}Delete
+ *     public void deleteWithFriends(User user, List&lt;User&gt; friends);
+ * }
+ * </pre>
+ *
+ * @see Insert
+ * @see Query
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Delete {
+}
diff --git a/room/common/src/main/java/androidx/room/Embedded.java b/room/common/src/main/java/androidx/room/Embedded.java
new file mode 100644
index 0000000..8890c93
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Embedded.java
@@ -0,0 +1,91 @@
+/*
+ * 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 androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Can be used as an annotation on a field of an {@link Entity} or {@code Pojo} to signal that
+ * nested fields (i.e. fields of the annotated field's class) can be referenced directly in the SQL
+ * queries.
+ * <p>
+ * If the container is an {@link Entity}, these sub fields will be columns in the {@link Entity}'s
+ * database table.
+ * <p>
+ * For example, if you have 2 classes:
+ * <pre>
+ *   public class Coordinates {
+ *       double latitude;
+ *       double longitude;
+ *   }
+ *   public class Address {
+ *       String street;
+ *       {@literal @}Embedded
+ *       Coordinates coordinates;
+ *   }
+ * </pre>
+ * Room will consider {@code latitude} and {@code longitude} as if they are fields of the
+ * {@code Address} class when mapping an SQLite row to {@code Address}.
+ * <p>
+ * So if you have a query that returns {@code street, latitude, longitude}, Room will properly
+ * construct an {@code Address} class.
+ * <p>
+ * If the {@code Address} class is annotated with {@link Entity}, its database table will have 3
+ * columns: {@code street, latitude, longitude}
+ * <p>
+ * If there is a name conflict with the fields of the sub object and the owner object, you can
+ * specify a {@link #prefix()} for the items of the sub object. Note that prefix is always applied
+ * to sub fields even if they have a {@link ColumnInfo} with a specific {@code name}.
+ * <p>
+ * If sub fields of an embedded field has {@link PrimaryKey} annotation, they <b>will not</b> be
+ * considered as primary keys in the owner {@link Entity}.
+ * <p>
+ * When an embedded field is read, if all fields of the embedded field (and its sub fields) are
+ * {@code null} in the {@link android.database.Cursor Cursor}, it is set to {@code null}. Otherwise,
+ * it is constructed.
+ * <p>
+ * Note that even if you have {@link TypeConverter}s that convert a {@code null} column into a
+ * {@code non-null} value, if all columns of the embedded field in the
+ * {@link android.database.Cursor Cursor} are null, the {@link TypeConverter} will never be called
+ * and the embedded field will not be constructed.
+ * <p>
+ * You can override this behavior by annotating the embedded field with
+ * {@link androidx.annotation.NonNull}.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Embedded {
+    /**
+     * Specifies a prefix to prepend the column names of the fields in the embedded fields.
+     * <p>
+     * For the example above, if we've written:
+     * <pre>
+     *   {@literal @}Embedded(prefix = "foo_")
+     *   Coordinates coordinates;
+     * </pre>
+     * The column names for {@code latitude} and {@code longitude} will be {@code foo_latitude} and
+     * {@code foo_longitude} respectively.
+     * <p>
+     * By default, prefix is the empty string.
+     *
+     * @return The prefix to be used for the fields of the embedded item.
+     */
+    String prefix() default  "";
+}
diff --git a/room/common/src/main/java/androidx/room/Entity.java b/room/common/src/main/java/androidx/room/Entity.java
new file mode 100644
index 0000000..fc69ada
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Entity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as an entity. This class will have a mapping SQLite table in the database.
+ * <p>
+ * Each entity must have at least 1 field annotated with {@link PrimaryKey}.
+ * You can also use {@link #primaryKeys()} attribute to define the primary key.
+ * <p>
+ * Each entity must either have a no-arg constructor or a constructor whose parameters match
+ * fields (based on type and name). Constructor does not have to receive all fields as parameters
+ * but if a field is not passed into the constructor, it should either be public or have a public
+ * setter. If a matching constructor is available, Room will always use it. If you don't want it
+ * to use a constructor, you can annotate it with {@link Ignore}.
+ * <p>
+ * 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
+ * public class User {
+ *   {@literal @}PrimaryKey
+ *   private final int uid;
+ *   private String name;
+ *   {@literal @}ColumnInfo(name = "last_name")
+ *   private String lastName;
+ *
+ *   public User(int uid) {
+ *       this.uid = uid;
+ *   }
+ *   public String getLastName() {
+ *       return lastName;
+ *   }
+ *   public void setLastName(String lastName) {
+ *       this.lastName = lastName;
+ *   }
+ * }
+ * </pre>
+ *
+ * @see Dao
+ * @see Database
+ * @see PrimaryKey
+ * @see ColumnInfo
+ * @see Index
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface Entity {
+    /**
+     * The table name in the SQLite database. If not set, defaults to the class name.
+     *
+     * @return The SQLite tableName of the Entity.
+     */
+    String tableName() default "";
+
+    /**
+     * List of indices on the table.
+     *
+     * @return The list of indices on the table.
+     */
+    Index[] indices() default {};
+
+    /**
+     * If set to {@code true}, any Index defined in parent classes of this class will be carried
+     * over to the current {@code Entity}. Note that if you set this to {@code true}, even if the
+     * {@code Entity} has a parent which sets this value to {@code false}, the {@code Entity} will
+     * still inherit indices from it and its parents.
+     * <p>
+     * When the {@code Entity} inherits an index from the parent, it is <b>always</b> renamed with
+     * the default naming schema since SQLite <b>does not</b> allow using the same index name in
+     * multiple tables. See {@link Index} for the details of the default name.
+     * <p>
+     * By default, indices defined in parent classes are dropped to avoid unexpected indices.
+     * When this happens, you will receive a {@link RoomWarnings#INDEX_FROM_PARENT_FIELD_IS_DROPPED}
+     * or {@link RoomWarnings#INDEX_FROM_PARENT_IS_DROPPED} warning during compilation.
+     *
+     * @return True if indices from parent classes should be automatically inherited by this Entity,
+     *         false otherwise. Defaults to false.
+     */
+    boolean inheritSuperIndices() default false;
+
+    /**
+     * The list of Primary Key column names.
+     * <p>
+     * If you would like to define an auto generated primary key, you can use {@link PrimaryKey}
+     * annotation on the field with {@link PrimaryKey#autoGenerate()} set to {@code true}.
+     *
+     * @return The primary key of this Entity. Can be empty if the class has a field annotated
+     * with {@link PrimaryKey}.
+     */
+    String[] primaryKeys() default {};
+
+    /**
+     * List of {@link ForeignKey} constraints on this entity.
+     *
+     * @return The list of {@link ForeignKey} constraints on this entity.
+     */
+    ForeignKey[] foreignKeys() default {};
+}
diff --git a/room/common/src/main/java/androidx/room/ForeignKey.java b/room/common/src/main/java/androidx/room/ForeignKey.java
new file mode 100644
index 0000000..847941a
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/ForeignKey.java
@@ -0,0 +1,166 @@
+/*
+ * 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 androidx.room;
+
+import androidx.annotation.IntDef;
+
+/**
+ * Declares a foreign key on another {@link Entity}.
+ * <p>
+ * Foreign keys allows you to specify constraints across Entities such that SQLite will ensure that
+ * the relationship is valid when you modify the database.
+ * <p>
+ * When a foreign key constraint is specified, SQLite requires the referenced columns to be part of
+ * a unique index in the parent table or the primary key of that table. You must create a unique
+ * index in the parent entity that covers the referenced columns (Room will verify this at compile
+ * time and print an error if it is missing).
+ * <p>
+ * It is also recommended to create an index on the child table to avoid full table scans when the
+ * parent table is modified. If a suitable index on the child table is missing, Room will print
+ * {@link RoomWarnings#MISSING_INDEX_ON_FOREIGN_KEY_CHILD} warning.
+ * <p>
+ * A foreign key constraint can be deferred until the transaction is complete. This is useful if
+ * you are doing bulk inserts into the database in a single transaction. By default, foreign key
+ * constraints are immediate but you can change this value by setting {@link #deferred()} to
+ * {@code true}. You can also use
+ * <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>
+ * documentation for details.
+ */
+public @interface ForeignKey {
+    /**
+     * The parent Entity to reference. It must be a class annotated with {@link Entity} and
+     * referenced in the same database.
+     *
+     * @return The parent Entity.
+     */
+    Class entity();
+
+    /**
+     * The list of column names in the parent {@link Entity}.
+     * <p>
+     * Number of columns must match the number of columns specified in {@link #childColumns()}.
+     *
+     * @return The list of column names in the parent Entity.
+     * @see #childColumns()
+     */
+    String[] parentColumns();
+
+    /**
+     * The list of column names in the current {@link Entity}.
+     * <p>
+     * Number of columns must match the number of columns specified in {@link #parentColumns()}.
+     *
+     * @return The list of column names in the current Entity.
+     */
+    String[] childColumns();
+
+    /**
+     * Action to take when the parent {@link Entity} is deleted from the database.
+     * <p>
+     * By default, {@link #NO_ACTION} is used.
+     *
+     * @return The action to take when the referenced entity is deleted from the database.
+     */
+    @Action int onDelete() default NO_ACTION;
+
+    /**
+     * Action to take when the parent {@link Entity} is updated in the database.
+     * <p>
+     * By default, {@link #NO_ACTION} is used.
+     *
+     * @return The action to take when the referenced entity is updated in the database.
+     */
+    @Action int onUpdate() default NO_ACTION;
+
+    /**
+     * * A foreign key constraint can be deferred until the transaction is complete. This is useful
+     * if you are doing bulk inserts into the database in a single transaction. By default, foreign
+     * key constraints are immediate but you can change it by setting this field to {@code true}.
+     * You can also use
+     * <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a>
+     * PRAGMA to defer them depending on your transaction.
+     *
+     * @return Whether the foreign key constraint should be deferred until the transaction is
+     * complete. Defaults to {@code false}.
+     */
+    boolean deferred() default false;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * When a parent key is modified or deleted from the database, no special action is taken.
+     * This means that SQLite will not make any effort to fix the constraint failure, instead,
+     * reject the change.
+     */
+    int NO_ACTION = 1;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * The RESTRICT action means that the application is prohibited from deleting
+     * (for {@link #onDelete()}) or modifying (for {@link #onUpdate()}) a parent key when there
+     * exists one or more child keys mapped to it. The difference between the effect of a RESTRICT
+     * action and normal foreign key constraint enforcement is that the RESTRICT action processing
+     * happens as soon as the field is updated - not at the end of the current statement as it would
+     * with an immediate constraint, or at the end of the current transaction as it would with a
+     * {@link #deferred()} constraint.
+     * <p>
+     * Even if the foreign key constraint it is attached to is {@link #deferred()}, configuring a
+     * RESTRICT action causes SQLite to return an error immediately if a parent key with dependent
+     * child keys is deleted or modified.
+     */
+    int RESTRICT = 2;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * If the configured action is "SET NULL", then when a parent key is deleted
+     * (for {@link #onDelete()}) or modified (for {@link #onUpdate()}), the child key columns of all
+     * rows in the child table that mapped to the parent key are set to contain {@code NULL} values.
+     */
+    int SET_NULL = 3;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * The "SET DEFAULT" actions are similar to {@link #SET_NULL}, except that each of the child key
+     * columns is set to contain the columns default value instead of {@code NULL}.
+     */
+    int SET_DEFAULT = 4;
+
+    /**
+     * Possible value for {@link #onDelete()} or {@link #onUpdate()}.
+     * <p>
+     * A "CASCADE" action propagates the delete or update operation on the parent key to each
+     * dependent child key. For {@link #onDelete()} action, this means that each row in the child
+     * entity that was associated with the deleted parent row is also deleted. For an
+     * {@link #onUpdate()} action, it means that the values stored in each dependent child key are
+     * modified to match the new parent key values.
+     */
+    int CASCADE = 5;
+
+    /**
+     * Constants definition for values that can be used in {@link #onDelete()} and
+     * {@link #onUpdate()}.
+     */
+    @IntDef({NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT, CASCADE})
+    @interface Action {
+    }
+}
diff --git a/room/common/src/main/java/androidx/room/Ignore.java b/room/common/src/main/java/androidx/room/Ignore.java
new file mode 100644
index 0000000..011b949
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Ignore.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Ignores the marked element from Room's processing logic.
+ * <p>
+ * This annotation can be used in multiple places where Room processor runs. For instance, you can
+ * add it to a field of an {@link Entity} and Room will not persist that field.
+ */
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface Ignore {
+}
diff --git a/room/common/src/main/java/androidx/room/Index.java b/room/common/src/main/java/androidx/room/Index.java
new file mode 100644
index 0000000..0e30145
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Index.java
@@ -0,0 +1,74 @@
+/*
+ * 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 androidx.room;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declares an index on an Entity.
+ * see: <a href="https://sqlite.org/lang_createindex.html">SQLite Index Documentation</a>
+ * <p>
+ * Adding an index usually speeds up your select queries but will slow down other queries like
+ * insert or update. You should be careful when adding indices to ensure that this additional cost
+ * is worth the gain.
+ * <p>
+ * There are 2 ways to define an index in an {@link Entity}. You can either set
+ * {@link ColumnInfo#index()} property to index individual fields or define composite indices via
+ * {@link Entity#indices()}.
+ * <p>
+ * If an indexed field is embedded into another Entity via {@link Embedded}, it is <b>NOT</b>
+ * added as an index to the containing {@link Entity}. If you want to keep it indexed, you must
+ * re-declare it in the containing {@link Entity}.
+ * <p>
+ * Similarly, if an {@link Entity} extends another class, indices from the super classes are
+ * <b>NOT</b> inherited. You must re-declare them in the child {@link Entity} or set
+ * {@link Entity#inheritSuperIndices()} to {@code true}.
+ * */
+@Target({})
+@Retention(RetentionPolicy.CLASS)
+public @interface Index {
+    /**
+     * List of column names in the Index.
+     * <p>
+     * The order of columns is important as it defines when SQLite can use a particular index.
+     * See <a href="https://www.sqlite.org/optoverview.html">SQLite documentation</a> for details on
+     * index usage in the query optimizer.
+     *
+     * @return The list of column names in the Index.
+     */
+    String[] value();
+
+    /**
+     * Name of the index. If not set, Room will set it to the list of columns joined by '_' and
+     * prefixed by "index_${tableName}". So if you have a table with name "Foo" and with an index
+     * of {"bar", "baz"}, generated index name will be  "index_Foo_bar_baz". If you need to specify
+     * the index in a query, you should never rely on this name, instead, specify a name for your
+     * index.
+     *
+     * @return The name of the index.
+     */
+    String name() default "";
+
+    /**
+     * If set to true, this will be a unique index and any duplicates will be rejected.
+     *
+     * @return True if index is unique. False by default.
+     */
+    boolean unique() default false;
+}
diff --git a/room/common/src/main/java/androidx/room/Insert.java b/room/common/src/main/java/androidx/room/Insert.java
new file mode 100644
index 0000000..b8378fb
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Insert.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.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 a {@link Dao} annotated class as an insert method.
+ * <p>
+ * The implementation of the method will insert its parameters into the database.
+ * <p>
+ * All of the parameters of the Insert method must either be classes annotated with {@link Entity}
+ * or collections/array of it.
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Dao
+ * public interface MyDao {
+ *     {@literal @}Insert(onConflict = OnConflictStrategy.REPLACE)
+ *     public void insertUsers(User... users);
+ *     {@literal @}Insert
+ *     public void insertBoth(User user1, User user2);
+ *     {@literal @}Insert
+ *     public void insertWithFriends(User user, List&lt;User&gt; friends);
+ * }
+ * </pre>
+ *
+ * @see Update
+ * @see Delete
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface Insert {
+    /**
+     * What to do if a conflict happens.
+     * @see <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a>
+     *
+     * @return How to handle conflicts. Defaults to {@link OnConflictStrategy#ABORT}.
+     */
+    @OnConflictStrategy
+    int onConflict() default OnConflictStrategy.ABORT;
+}
diff --git a/room/common/src/main/java/androidx/room/OnConflictStrategy.java b/room/common/src/main/java/androidx/room/OnConflictStrategy.java
new file mode 100644
index 0000000..e27e9e1
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/OnConflictStrategy.java
@@ -0,0 +1,56 @@
+/*
+ * 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 androidx.room;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Set of conflict handling strategies for various {@link Dao} methods.
+ * <p>
+ * Check <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a> for
+ * details.
+ */
+@Retention(SOURCE)
+@IntDef({OnConflictStrategy.REPLACE, OnConflictStrategy.ROLLBACK, OnConflictStrategy.ABORT,
+        OnConflictStrategy.FAIL, OnConflictStrategy.IGNORE})
+public @interface OnConflictStrategy {
+    /**
+     * OnConflict strategy constant to replace the old data and continue the transaction.
+     */
+    int REPLACE = 1;
+    /**
+     * OnConflict strategy constant to rollback the transaction.
+     */
+    int ROLLBACK = 2;
+    /**
+     * OnConflict strategy constant to abort the transaction.
+     */
+    int ABORT = 3;
+    /**
+     * OnConflict strategy constant to fail the transaction.
+     */
+    int FAIL = 4;
+    /**
+     * OnConflict strategy constant to ignore the conflict.
+     */
+    int IGNORE = 5;
+
+}
diff --git a/room/common/src/main/java/androidx/room/PrimaryKey.java b/room/common/src/main/java/androidx/room/PrimaryKey.java
new file mode 100644
index 0000000..7b01221
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/PrimaryKey.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a field in an {@link Entity} as the primary key.
+ * <p>
+ * If you would like to define a composite primary key, you should use {@link Entity#primaryKeys()}
+ * method.
+ * <p>
+ * Each {@link Entity} must declare a primary key unless one of its super classes declares a
+ * primary key. If both an {@link Entity} and its super class defines a {@code PrimaryKey}, the
+ * child's {@code PrimaryKey} definition will override the parent's {@code PrimaryKey}.
+ * <p>
+ * If {@code PrimaryKey} annotation is used on a {@link Embedded}d field, all columns inherited
+ * from that embedded field becomes the composite primary key (including its grand children
+ * fields).
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface PrimaryKey {
+    /**
+     * Set to true to let SQLite generate the unique id.
+     * <p>
+     * When set to {@code true}, the SQLite type affinity for the field should be {@code INTEGER}.
+     * <p>
+     * If the field type is {@code long} or {@code int} (or its TypeConverter converts it to a
+     * {@code long} or {@code int}), {@link Insert} methods treat {@code 0} as not-set while
+     * inserting the item.
+     * <p>
+     * If the field's type is {@link Integer} or {@link Long} (or its TypeConverter converts it to
+     * an {@link Integer} or a {@link Long}), {@link Insert} methods treat {@code null} as
+     * not-set while inserting the item.
+     *
+     * @return Whether the primary key should be auto-generated by SQLite or not. Defaults
+     * to false.
+     */
+    boolean autoGenerate() default false;
+}
diff --git a/room/common/src/main/java/androidx/room/Query.java b/room/common/src/main/java/androidx/room/Query.java
new file mode 100644
index 0000000..34af09b
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Query.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.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 a {@link Dao} annotated class as a query method.
+ * <p>
+ * The value of the annotation includes the query that will be run when this method is called. This
+ * query is <b>verified at compile time</b> by Room to ensure that it compiles fine against the
+ * database.
+ * <p>
+ * The arguments of the method will be bound to the bind arguments in the SQL statement. See
+ * <href="https://www.sqlite.org/c3ref/bind_blob.html">SQLite's binding documentation</> for
+ * details of bind arguments in SQLite.
+ * <p>
+ * Room only supports named bind parameter {@code :name} to avoid any confusion between the
+ * method parameters and the query bind parameters.
+ * <p>
+ * Room will automatically bind the parameters of the method into the bind arguments. This is done
+ * by matching the name of the parameters to the name of the bind arguments.
+ * <pre>
+ *     {@literal @}Query("SELECT * FROM user WHERE user_name LIKE :name AND last_name LIKE :last")
+ *     public abstract List&lt;User&gt; findUsersByNameAndLastName(String name, String last);
+ * </pre>
+ * <p>
+ * As an extension over SQLite bind arguments, Room supports binding a list of parameters to the
+ * query. At runtime, Room will build the correct query to have matching number of bind arguments
+ * depending on the number of items in the method parameter.
+ * <pre>
+ *     {@literal @}Query("SELECT * FROM user WHERE uid IN(:userIds)")
+ *     public abstract List<User> findByIds(int[] userIds);
+ * </pre>
+ * For the example above, if the {@code userIds} is an array of 3 elements, Room will run the
+ * query as: {@code SELECT * FROM user WHERE uid IN(?, ?, ?)} and bind each item in the
+ * {@code userIds} array into the statement.
+ * <p>
+ * There are 3 types of queries supported in {@code Query} methods: SELECT, UPDATE and DELETE.
+ * <p>
+ * For SELECT queries, Room will infer the result contents from the method's return type and
+ * generate the code that will automatically convert the query result into the method's return
+ * type. For single result queries, the return type can be any java object. For queries that return
+ * multiple values, you can use {@link java.util.List} or {@code Array}. In addition to these, any
+ * query may return {@link android.database.Cursor Cursor} or any query result can be wrapped in
+ * a {@link androidx.lifecycle.LiveData LiveData}.
+ * <p>
+ * <b>RxJava2</b> If you are using RxJava2, you can also return {@code Flowable<T>} or
+ * {@code Publisher<T>} from query methods. Since Reactive Streams does not allow {@code null}, if
+ * the query returns a nullable type, it will not dispatch anything if the value is {@code null}
+ * (like fetching an {@link Entity} row that does not exist).
+ * You can return {@code Flowable<T[]>} or {@code Flowable<List<T>>} to workaround this limitation.
+ * <p>
+ * Both {@code Flowable<T>} and {@code Publisher<T>} will observe the database for changes and
+ * re-dispatch if data changes. If you want to query the database without observing changes, you can
+ * use {@code Maybe<T>} or {@code Single<T>}. If a {@code Single<T>} query returns {@code null},
+ * Room will throw
+ * {@link androidx.room.EmptyResultSetException EmptyResultSetException}.
+ * <p>
+ * UPDATE or DELETE queries can return {@code void} or {@code int}. If it is an {@code int},
+ * the value is the number of rows affected by this query.
+ * <p>
+ * You can return arbitrary POJOs from your query methods as long as the fields of the POJO match
+ * the column names in the query result.
+ * For example, if you have class:
+ * <pre>
+ * class UserName {
+ *     public String name;
+ *     {@literal @}ColumnInfo(name = "last_name")
+ *     public String lastName;
+ * }
+ * </pre>
+ * You can write a query like this:
+ * <pre>
+ *     {@literal @}Query("SELECT last_name, name FROM user WHERE uid = :userId LIMIT 1")
+ *     public abstract UserName findOneUserName(int userId);
+ * </pre>
+ * And Room will create the correct implementation to convert the query result into a
+ * {@code UserName} object. If there is a mismatch between the query result and the fields of the
+ * POJO, as long as there is at least 1 field match, Room prints a
+ * {@link RoomWarnings#CURSOR_MISMATCH} warning and sets as many fields as it can.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Query {
+    /**
+     * The SQLite query to be run.
+     * @return The query to be run.
+     */
+    String value();
+}
diff --git a/room/common/src/main/java/androidx/room/RawQuery.java b/room/common/src/main/java/androidx/room/RawQuery.java
new file mode 100644
index 0000000..5abcecc
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/RawQuery.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2018 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 androidx.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 a {@link Dao} annotated class as a raw query method where you can pass the
+ * query as a {@link androidx.sqlite.db.SupportSQLiteQuery SupportSQLiteQuery}.
+ * <pre>
+ * {@literal @}Dao
+ * interface RawDao {
+ *     {@literal @}RawQuery
+ *     User getUserViaQuery(SupportSQLiteQuery query);
+ * }
+ * SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM User WHERE id = ? LIMIT 1",
+ *         new Object[]{userId});
+ * User user2 = rawDao.getUserViaQuery(query);
+ * </pre>
+ * <p>
+ * Room will generate the code based on the return type of the function and failure to
+ * pass a proper query will result in a runtime failure or an undefined result.
+ * <p>
+ * If you know the query at compile time, you should always prefer {@link Query} since it validates
+ * the query at compile time and also generates more efficient code since Room can compute the
+ * query result at compile time (e.g. it does not need to account for possibly missing columns in
+ * the response).
+ * <p>
+ * On the other hand, {@code RawQuery} serves as an escape hatch where you can build your own
+ * SQL query at runtime but still use Room to convert it into objects.
+ * <p>
+ * {@code RawQuery} methods must return a non-void type. If you want to execute a raw query that
+ * does not return any value, use {@link androidx.room.RoomDatabase#query
+ * RoomDatabase#query} methods.
+ * <p>
+ * RawQuery methods can only be used for read queries. For write queries, use
+ * {@link androidx.room.RoomDatabase#getOpenHelper
+ * RoomDatabase.getOpenHelper().getWritableDatabase()}.
+ * <p>
+ * <b>Observable Queries:</b>
+ * <p>
+ * {@code RawQuery} methods can return observable types but you need to specify which tables are
+ * accessed in the query using the {@link #observedEntities()} field in the annotation.
+ * <pre>
+ * {@literal @}Dao
+ * interface RawDao {
+ *     {@literal @}RawQuery(observedEntities = User.class)
+ *     LiveData&lt;List&lt;User>> getUsers(SupportSQLiteQuery query);
+ * }
+ * LiveData&lt;List&lt;User>> liveUsers = rawDao.getUsers(
+ *     new SimpleSQLiteQuery("SELECT * FROM User ORDER BY name DESC"));
+ * </pre>
+ * <b>Returning Pojos:</b>
+ * <p>
+ * RawQueries can also return plain old java objects, similar to {@link Query} methods.
+ * <pre>
+ * public class NameAndLastName {
+ *     public final String name;
+ *     public final String lastName;
+ *
+ *     public NameAndLastName(String name, String lastName) {
+ *         this.name = name;
+ *         this.lastName = lastName;
+ *     }
+ * }
+ *
+ * {@literal @}Dao
+ * interface RawDao {
+ *     {@literal @}RawQuery
+ *     NameAndLastName getNameAndLastName(SupportSQLiteQuery query);
+ * }
+ * NameAndLastName result = rawDao.getNameAndLastName(
+ *      new SimpleSQLiteQuery("SELECT * FROM User WHERE id = ?", new Object[]{userId}))
+ * // or
+ * NameAndLastName result = rawDao.getNameAndLastName(
+ *      new SimpleSQLiteQuery("SELECT name, lastName FROM User WHERE id = ?",
+ *          new Object[]{userId})))
+ * </pre>
+ * <p>
+ * <b>Pojos with Embedded Fields:</b>
+ * <p>
+ * {@code RawQuery} methods can return pojos that include {@link Embedded} fields as well.
+ * <pre>
+ * public class UserAndPet {
+ *     {@literal @}Embedded
+ *     public User user;
+ *     {@literal @}Embedded
+ *     public Pet pet;
+ * }
+ *
+ * {@literal @}Dao
+ * interface RawDao {
+ *     {@literal @}RawQuery
+ *     UserAndPet getUserAndPet(SupportSQLiteQuery query);
+ * }
+ * UserAndPet received = rawDao.getUserAndPet(
+ *         new SimpleSQLiteQuery("SELECT * FROM User, Pet WHERE User.id = Pet.userId LIMIT 1"))
+ * </pre>
+ *
+ * <b>Relations:</b>
+ * <p>
+ * {@code RawQuery} return types can also be objects with {@link Relation Relations}.
+ * <pre>
+ * public class UserAndAllPets {
+ *     {@literal @}Embedded
+ *     public User user;
+ *     {@literal @}Relation(parentColumn = "id", entityColumn = "userId")
+ *     public List&lt;Pet> pets;
+ * }
+ *
+ * {@literal @}Dao
+ * interface RawDao {
+ *     {@literal @}RawQuery
+ *     List&lt;UserAndAllPets> getUsersAndAllPets(SupportSQLiteQuery query);
+ * }
+ * List&lt;UserAndAllPets> result = rawDao.getUsersAndAllPets(
+ *      new SimpleSQLiteQuery("SELECT * FROM users"));
+ * </pre>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface RawQuery {
+    /**
+     * Denotes the list of entities which are accessed in the provided query and should be observed
+     * for invalidation if the query is observable.
+     * <p>
+     * The listed classes should either be annotated with {@link Entity} or they should reference to
+     * at least 1 Entity (via {@link Embedded} or {@link Relation}).
+     * <p>
+     * Providing this field in a non-observable query has no impact.
+     * <pre>
+     * {@literal @}Dao
+     * interface RawDao {
+     *     {@literal @}RawQuery(observedEntities = User.class)
+     *     LiveData&lt;List&lt;User>> getUsers(String query);
+     * }
+     * LiveData&lt;List&lt;User>> liveUsers = rawDao.getUsers("select * from User ORDER BY name
+     * DESC");
+     * </pre>
+     *
+     * @return List of entities that should invalidate the query if changed.
+     */
+    Class[] observedEntities() default {};
+}
diff --git a/room/common/src/main/java/androidx/room/Relation.java b/room/common/src/main/java/androidx/room/Relation.java
new file mode 100644
index 0000000..33dd31e
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Relation.java
@@ -0,0 +1,142 @@
+/*
+ * 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 androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A convenience annotation which can be used in a Pojo to automatically fetch relation entities.
+ * When the Pojo is returned from a query, all of its relations are also fetched by Room.
+ *
+ * <pre>
+ * {@literal @}Entity
+ * public class Pet {
+ *     {@literal @} PrimaryKey
+ *     int id;
+ *     int userId;
+ *     String name;
+ *     // other fields
+ * }
+ * public class UserNameAndAllPets {
+ *   public int id;
+ *   public String name;
+ *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId")
+ *   public List&lt;Pet&gt; pets;
+ * }
+ *
+ * {@literal @}Dao
+ * public interface UserPetDao {
+ *     {@literal @}Query("SELECT id, name from User")
+ *     public List&lt;UserNameAndAllPets&gt; loadUserAndPets();
+ * }
+ * </pre>
+ * <p>
+ * The type of the field annotated with {@code Relation} must be a {@link java.util.List} or
+ * {@link java.util.Set}. By default, the {@link Entity} type is inferred from the return type.
+ * If you would like to return a different object, you can specify the {@link #entity()} property
+ * in the annotation.
+ * <pre>
+ * public class User {
+ *     int id;
+ *     // other fields
+ * }
+ * public class PetNameAndId {
+ *     int id;
+ *     String name;
+ * }
+ * public class UserAllPets {
+ *   {@literal @}Embedded
+ *   public User user;
+ *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
+ *   public List&lt;PetNameAndId&gt; pets;
+ * }
+ * {@literal @}Dao
+ * public interface UserPetDao {
+ *     {@literal @}Query("SELECT * from User")
+ *     public List&lt;UserAllPets&gt; loadUserAndPets();
+ * }
+ * </pre>
+ * <p>
+ * In the example above, {@code PetNameAndId} is a regular Pojo but all of fields are fetched
+ * from the {@code entity} defined in the {@code @Relation} annotation (<i>Pet</i>).
+ * {@code PetNameAndId} could also define its own relations all of which would also be fetched
+ * automatically.
+ * <p>
+ * If you would like to specify which columns are fetched from the child {@link Entity}, you can
+ * use {@link #projection()} property in the {@code Relation} annotation.
+ * <pre>
+ * public class UserAndAllPets {
+ *   {@literal @}Embedded
+ *   public User user;
+ *   {@literal @}Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class,
+ *           projection = {"name"})
+ *   public List&lt;String&gt; petNames;
+ * }
+ * </pre>
+ * <p>
+ * Note that {@code @Relation} annotation can be used only in Pojo classes, an {@link Entity} class
+ * cannot have relations. This is a design decision to avoid common pitfalls in {@link Entity}
+ * setups. You can read more about it in the main Room documentation. When loading data, you can
+ * simply work around this limitation by creating Pojo classes that extend the {@link Entity}.
+ * <p>
+ * Note that the {@code @Relation} annotated field cannot be a constructor parameter, it must be
+ * public or have a public setter.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface Relation {
+    /**
+     * The entity to fetch the item from. You don't need to set this if the entity matches the
+     * type argument in the return type.
+     *
+     * @return The entity to fetch from. By default, inherited from the return type.
+     */
+    Class entity() default Object.class;
+
+    /**
+     * Reference field in the parent Pojo.
+     * <p>
+     * If you would like to access to a sub item of a {@link Embedded}d field, you can use
+     * the {@code .} notation.
+     * <p>
+     * For instance, if you have a {@link Embedded}d field named {@code user} with a sub field
+     * {@code id}, you can reference it via {@code user.id}.
+     * <p>
+     * This value will be matched against the value defined in {@link #entityColumn()}.
+     *
+     * @return The field reference in the parent object.
+     */
+    String parentColumn();
+
+    /**
+     * The field path to match in the {@link #entity()}. This value will be matched against the
+     * value defined in {@link #parentColumn()}.
+     */
+    String entityColumn();
+
+    /**
+     * If sub fields should be fetched from the entity, you can specify them using this field.
+     * <p>
+     * By default, inferred from the the return type.
+     *
+     * @return The list of columns to be selected from the {@link #entity()}.
+     */
+    String[] projection() default {};
+}
diff --git a/room/common/src/main/java/androidx/room/RoomMasterTable.java b/room/common/src/main/java/androidx/room/RoomMasterTable.java
new file mode 100644
index 0000000..6c8c855
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/RoomMasterTable.java
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.room;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Schema information about Room's master table.
+ *
+ * @hide
+ */
+@SuppressWarnings("WeakerAccess")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class RoomMasterTable {
+    /**
+     * The master table where room keeps its metadata information.
+     */
+    public static final String TABLE_NAME = "room_master_table";
+    // must match the runtime property Room#MASTER_TABLE_NAME
+    public static final String NAME = "room_master_table";
+    private static final String COLUMN_ID = "id";
+    private static final String COLUMN_IDENTITY_HASH = "identity_hash";
+    public static final String DEFAULT_ID = "42";
+
+    public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+            + COLUMN_ID + " INTEGER PRIMARY KEY,"
+            + COLUMN_IDENTITY_HASH + " TEXT)";
+
+    public static final String READ_QUERY = "SELECT " + COLUMN_IDENTITY_HASH
+            + " FROM " + TABLE_NAME + " WHERE "
+            + COLUMN_ID + " = " + DEFAULT_ID + " LIMIT 1";
+
+    /**
+     * We don't escape here since we know what we are passing.
+     */
+    public static String createInsertQuery(String hash) {
+        return "INSERT OR REPLACE INTO " + TABLE_NAME + " ("
+                + COLUMN_ID + "," + COLUMN_IDENTITY_HASH + ")"
+                + " VALUES(" + DEFAULT_ID + ", \"" + hash + "\")";
+    }
+}
diff --git a/room/common/src/main/java/androidx/room/RoomWarnings.java b/room/common/src/main/java/androidx/room/RoomWarnings.java
new file mode 100644
index 0000000..a4122bd
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/RoomWarnings.java
@@ -0,0 +1,136 @@
+/*
+ * 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 androidx.room;
+
+/**
+ * The list of warnings that are produced by Room.
+ * <p>
+ * You can use these values inside a {@link SuppressWarnings} annotation to disable the warnings.
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class RoomWarnings {
+    /**
+     * The warning dispatched by Room when the return value of a {@link Query} method does not
+     * exactly match the fields in the query result.
+     */
+    // if you change this, don't forget to change androidx.room.vo.Warning
+    public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+
+    /**
+     * Reported when Room cannot verify database queries during compilation due to lack of
+     * tmp dir access in JVM.
+     */
+    public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+
+    /**
+     * Reported when Room cannot verify database queries during compilation. This usually happens
+     * when it cannot find the SQLite JDBC driver on the host machine.
+     * <p>
+     * Room can function without query verification but its functionality will be limited.
+     */
+    public static final String CANNOT_CREATE_VERIFICATION_DATABASE =
+            "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+
+    /**
+     * Reported when an {@link Entity} field that is annotated with {@link Embedded} has a
+     * sub field which is annotated with {@link PrimaryKey} but the {@link PrimaryKey} is dropped
+     * while composing it into the parent object.
+     */
+    public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED =
+            "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity} field that is annotated with {@link Embedded} has a
+     * sub field which has a {@link ColumnInfo} annotation with {@code index = true}.
+     * <p>
+     * You can re-define the index in the containing {@link Entity}.
+     */
+    public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED =
+            "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity} that has a {@link Embedded}d field whose type is another
+     * {@link Entity} and that {@link Entity} has some indices defined.
+     * These indices will NOT be created in the containing {@link Entity}. If you want to preserve
+     * them, you can re-define them in the containing {@link Entity}.
+     */
+    public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED =
+            "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity}'s parent declares an {@link Index}. Room does not
+     * automatically inherit these indices to avoid hidden costs or unexpected constraints.
+     * <p>
+     * If you want your child class to have the indices of the parent, you must re-declare
+     * them in the child class. Alternatively, you can set {@link Entity#inheritSuperIndices()}
+     * to {@code true}.
+     */
+    public static final String INDEX_FROM_PARENT_IS_DROPPED =
+            "ROOM_PARENT_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when an {@link Entity} inherits a field from its super class and the field has a
+     * {@link ColumnInfo} annotation with {@code index = true}.
+     * <p>
+     * These indices are dropped for the {@link Entity} and you would need to re-declare them if
+     * you want to keep them. Alternatively, you can set {@link Entity#inheritSuperIndices()}
+     * to {@code true}.
+     */
+    public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED =
+            "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+
+    /**
+     * Reported when a {@link Relation} {@link Entity}'s SQLite column type does not match the type
+     * in the parent. Room will still do the matching using {@code String} representations.
+     */
+    public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+
+    /**
+     * Reported when a `room.schemaLocation` argument is not provided into the annotation processor.
+     * You can either set {@link Database#exportSchema()} to {@code false} or provide
+     * `room.schemaLocation` to the annotation processor. You are strongly adviced to provide it
+     * and also commit them into your version control system.
+     */
+    public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+
+    /**
+     * When there is a foreign key from Entity A to Entity B, it is a good idea to index the
+     * reference columns in B, otherwise, each modification on Entity A will trigger a full table
+     * scan on Entity B.
+     * <p>
+     * If Room cannot find a proper index in the child entity (Entity B in this case), Room will
+     * print this warning.
+     */
+    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";
+
+    /**
+     * Reported when a @Query method returns a Pojo that has relations but the method is not
+     * annotated with @Transaction. Relations are run as separate queries and if the query is not
+     * run inside a transaction, it might return inconsistent results from the database.
+     */
+    public static final String RELATION_QUERY_WITHOUT_TRANSACTION =
+            "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+}
diff --git a/room/common/src/main/java/androidx/room/SkipQueryVerification.java b/room/common/src/main/java/androidx/room/SkipQueryVerification.java
new file mode 100644
index 0000000..9d49746
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/SkipQueryVerification.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 androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Skips database verification for the annotated element.
+ * <p>
+ * If it is a class annotated with {@link Database}, none of the queries for the database will
+ * be verified at compile time.
+ * <p>
+ * If it is a class annotated with {@link Dao}, none of the queries in the Dao class will
+ * be verified at compile time.
+ * <p>
+ * If it is a method in a Dao class, just the method's sql verification will be skipped.
+ * <p>
+ * You should use this as the last resort if Room cannot properly understand your query and you are
+ * 100% sure it works. Removing validation may limit the functionality of Room since it won't be
+ * able to understand the query response.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SkipQueryVerification {
+}
diff --git a/room/common/src/main/java/androidx/room/Transaction.java b/room/common/src/main/java/androidx/room/Transaction.java
new file mode 100644
index 0000000..0941673
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Transaction.java
@@ -0,0 +1,84 @@
+/*
+ * 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 androidx.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 a {@link Dao} class as a transaction method.
+ * <p>
+ * When used on a non-abstract method of an abstract {@link Dao} class,
+ * 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>
+ * <p>
+ * When used on a {@link Query} method that has a {@code Select} statement, the generated code for
+ * the Query will be run in a transaction. There are 2 main cases where you may want to do that:
+ * <ol>
+ *     <li>If the result of the query is fairly big, it is better to run it inside a transaction
+ *     to receive a consistent result. Otherwise, if the query result does not fit into a single
+ *     {@link android.database.CursorWindow CursorWindow}, the query result may be corrupted due to
+ *     changes in the database in between cursor window swaps.
+ *     <li>If the result of the query is a Pojo with {@link Relation} fields, these fields are
+ *     queried separately. To receive consistent results between these queries, you probably want
+ *     to run them in a single transaction.
+ * </ol>
+ * Example:
+ * <pre>
+ * class ProductWithReviews extends Product {
+ *     {@literal @}Relation(parentColumn = "id", entityColumn = "productId", entity = Review.class)
+ *     public List&lt;Review> reviews;
+ * }
+ * {@literal @}Dao
+ * public interface ProductDao {
+ *     {@literal @}Transaction {@literal @}Query("SELECT * from products")
+ *     public List&lt;ProductWithReviews> loadAll();
+ * }
+ * </pre>
+ * If the query is an async query (e.g. returns a {@link androidx.lifecycle.LiveData LiveData}
+ * or RxJava Flowable, the transaction is properly handled when the query is run, not when the
+ * method is called.
+ * <p>
+ * Putting this annotation on an {@link Insert}, {@link Update} or {@link Delete} method has no
+ * impact because they are always run inside a transaction. Similarly, if it is annotated with
+ * {@link Query} but runs an update or delete statement, it is automatically wrapped in a
+ * transaction.
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface Transaction {
+}
diff --git a/room/common/src/main/java/androidx/room/TypeConverter.java b/room/common/src/main/java/androidx/room/TypeConverter.java
new file mode 100644
index 0000000..b95ef09
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/TypeConverter.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 androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method as a type converter. A class can have as many @TypeConverter methods as it needs.
+ * <p>
+ * Each converter method should receive 1 parameter and have non-void return type.
+ *
+ * <pre>
+ * // example converter for java.util.Date
+ * public static class Converters {
+ *    {@literal @}TypeConverter
+ *    public Date fromTimestamp(Long value) {
+ *        return value == null ? null : new Date(value);
+ *    }
+ *
+ *    {@literal @}TypeConverter
+ *    public Long dateToTimestamp(Date date) {
+ *        if (date == null) {
+ *            return null;
+ *        } else {
+ *            return date.getTime();
+ *        }
+ *    }
+ *}
+ * </pre>
+ * @see TypeConverters
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TypeConverter {
+}
diff --git a/room/common/src/main/java/androidx/room/TypeConverters.java b/room/common/src/main/java/androidx/room/TypeConverters.java
new file mode 100644
index 0000000..e68525b
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/TypeConverters.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 androidx.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies additional type converters that Room can use. The TypeConverter is added to the scope
+ * of the element so if you put it on a class / interface, all methods / fields in that class will
+ * be able to use the converters.
+ * <ul>
+ * <li>If you put it on a {@link Database}, all Daos and Entities in that database will be able to
+ * use it.
+ * <li>If you put it on a {@link Dao}, all methods in the Dao will be able to use it.
+ * <li>If you put it on an {@link Entity}, all fields of the Entity will be able to use it.
+ * <li>If you put it on a POJO, all fields of the POJO will be able to use it.
+ * <li>If you put it on an {@link Entity} field, only that field will be able to use it.
+ * <li>If you put it on a {@link Dao} method, all parameters of the method will be able to use it.
+ * <li>If you put it on a {@link Dao} method parameter, just that field will be able to use it.
+ * </ul>
+ * @see TypeConverter
+ */
+@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TypeConverters {
+    /**
+     * The list of type converter classes. If converter methods are not static, Room will create
+     * an instance of these classes.
+     *
+     * @return The list of classes that contains the converter methods.
+     */
+    Class<?>[] value();
+}
diff --git a/room/common/src/main/java/androidx/room/Update.java b/room/common/src/main/java/androidx/room/Update.java
new file mode 100644
index 0000000..7482374
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/Update.java
@@ -0,0 +1,40 @@
+/*
+ * 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 androidx.room;
+
+/**
+ * Marks a method in a {@link Dao} annotated class as an update method.
+ * <p>
+ * The implementation of the method will update its parameters in the database if they already
+ * exists (checked by primary keys). If they don't already exists, this option will not change the
+ * database.
+ * <p>
+ * All of the parameters of the Update method must either be classes annotated with {@link Entity}
+ * or collections/array of it.
+ *
+ * @see Insert
+ * @see Delete
+ */
+public @interface Update {
+    /**
+     * What to do if a conflict happens.
+     * @see <a href="https://sqlite.org/lang_conflict.html">SQLite conflict documentation</a>
+     *
+     * @return How to handle conflicts. Defaults to {@link OnConflictStrategy#ABORT}.
+     */
+    @OnConflictStrategy
+    int onConflict() default OnConflictStrategy.ABORT;
+}
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
index 3599893..7a4370a 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -44,8 +44,8 @@
     // taken from ButterKnife
     def logger = new com.android.build.gradle.internal.LoggerWrapper(project.logger)
     def sdkHandler = new com.android.build.gradle.internal.SdkHandler(project, logger)
-    compile(project(":room:common"))
-    compile(project(":room:migration"))
+    compile(project(":room:room-common"))
+    compile(project(":room:room-migration"))
     compile(KOTLIN_STDLIB)
     compile(AUTO_COMMON)
     compile(JAVAPOET)
@@ -54,16 +54,16 @@
     compile(KOTLIN_METADATA)
     compile(APACHE_COMMONS_CODEC)
     testCompile(GOOGLE_COMPILE_TESTING)
-    testCompile project(":paging:common")
+    testCompile project(":paging:paging-common")
     testCompile(JUNIT)
     testCompile(INTELLIJ_ANNOTATIONS)
     testCompile(JSR250)
     testCompile(MOCKITO_CORE)
     testCompile fileTree(dir: "${sdkHandler.sdkFolder}/platforms/android-$SupportConfig.CURRENT_SDK_VERSION/",
             include : "android.jar")
-    testCompile fileTree(dir: "${new File(project(":room:runtime").buildDir, "libJar")}",
+    testCompile fileTree(dir: "${new File(project(":room:room-runtime").buildDir, "libJar")}",
             include : "*.jar")
-    testCompile fileTree(dir: "${new File(project(":persistence:db").buildDir, "libJar")}",
+    testCompile fileTree(dir: "${new File(project(":sqlite:sqlite").buildDir, "libJar")}",
             include : "*.jar")
     testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
 }
@@ -74,13 +74,13 @@
     inputs.file("$projectDir/SQLite.g4")
     classpath configurations.runtime
     main "org.antlr.v4.Tool"
-    args "SQLite.g4", "-visitor", "-o", new File(outFolder, "android/arch/persistence/room/parser").path,
-            "-package", "android.arch.persistence.room.parser"
+    args "SQLite.g4", "-visitor", "-o", new File(outFolder, "androidx/room/parser").path,
+            "-package", "androidx.room.parser"
 }
 
 tasks.findByName("compileKotlin").dependsOn(generateAntlrTask)
-tasks.findByName("compileKotlin").dependsOn(":room:runtime:jarDebug")
-tasks.findByName("compileKotlin").dependsOn(":persistence:db:jarDebug")
+tasks.findByName("compileKotlin").dependsOn(":room:room-runtime:jarDebug")
+tasks.findByName("compileKotlin").dependsOn(":sqlite:sqlite:jarDebug")
 
 supportLibrary {
     name = "Android Room Compiler"
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt
deleted file mode 100644
index 57070fd..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/RoomProcessor.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room
-
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.DatabaseProcessor
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.vo.DaoMethod
-import android.arch.persistence.room.vo.Warning
-import android.arch.persistence.room.writer.DaoWriter
-import android.arch.persistence.room.writer.DatabaseWriter
-import com.google.auto.common.BasicAnnotationProcessor
-import com.google.auto.common.MoreElements
-import com.google.common.collect.SetMultimap
-import java.io.File
-import javax.lang.model.SourceVersion
-import javax.lang.model.element.Element
-
-/**
- * The annotation processor for Room.
- */
-class RoomProcessor : BasicAnnotationProcessor() {
-    override fun initSteps(): MutableIterable<ProcessingStep>? {
-        val context = Context(processingEnv)
-        return arrayListOf(DatabaseProcessingStep(context))
-    }
-
-    override fun getSupportedOptions(): MutableSet<String> {
-        return Context.ARG_OPTIONS.toMutableSet()
-    }
-
-    override fun getSupportedSourceVersion(): SourceVersion {
-        return SourceVersion.latest()
-    }
-
-    class DatabaseProcessingStep(context: Context) : ContextBoundProcessingStep(context) {
-        override fun process(
-                elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>
-        ): MutableSet<Element> {
-            // TODO multi step support
-            val databases = elementsByAnnotation[Database::class.java]
-                    ?.map {
-                        DatabaseProcessor(context, MoreElements.asType(it)).process()
-                    }
-            val allDaoMethods = databases?.flatMap { it.daoMethods }
-            allDaoMethods?.let {
-                prepareDaosForWriting(databases, it)
-                it.forEach {
-                    DaoWriter(it.dao, context.processingEnv).write(context.processingEnv)
-                }
-            }
-
-            databases?.forEach { db ->
-                DatabaseWriter(db).write(context.processingEnv)
-                if (db.exportSchema) {
-                    val schemaOutFolder = context.schemaOutFolder
-                    if (schemaOutFolder == null) {
-                        context.logger.w(Warning.MISSING_SCHEMA_LOCATION, db.element,
-                                ProcessorErrors.MISSING_SCHEMA_EXPORT_DIRECTORY)
-                    } else {
-                        if (!schemaOutFolder.exists()) {
-                            schemaOutFolder.mkdirs()
-                        }
-                        val qName = db.element.qualifiedName.toString()
-                        val dbSchemaFolder = File(schemaOutFolder, qName)
-                        if (!dbSchemaFolder.exists()) {
-                            dbSchemaFolder.mkdirs()
-                        }
-                        db.exportSchema(File(dbSchemaFolder, "${db.version}.json"))
-                    }
-                }
-            }
-            return mutableSetOf()
-        }
-        override fun annotations(): MutableSet<out Class<out Annotation>> {
-            return mutableSetOf(Database::class.java, Dao::class.java, Entity::class.java)
-        }
-
-        /**
-         * Traverses all dao methods and assigns them suffix if they are used in multiple databases.
-         */
-        private fun prepareDaosForWriting(
-                databases: List<android.arch.persistence.room.vo.Database>,
-                daoMethods: List<DaoMethod>) {
-            daoMethods.groupBy { it.dao.typeName }
-                    // if used only in 1 database, nothing to do.
-                    .filter { entry -> entry.value.size > 1 }
-                    .forEach { entry ->
-                        entry.value.groupBy { daoMethod ->
-                            // first suffix guess: Database's simple name
-                            val db = databases.first { db -> db.daoMethods.contains(daoMethod) }
-                            db.typeName.simpleName()
-                        }.forEach { (dbName, methods) ->
-                            if (methods.size == 1) {
-                                //good, db names do not clash, use db name as suffix
-                                methods.first().dao.setSuffix(dbName)
-                            } else {
-                                // ok looks like a dao is used in 2 different databases both of
-                                // which have the same name. enumerate.
-                                methods.forEachIndexed { index, method ->
-                                    method.dao.setSuffix("${dbName}_$index")
-                                }
-                            }
-                        }
-                    }
-        }
-    }
-
-    abstract class ContextBoundProcessingStep(val context: Context) : ProcessingStep
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/KotlinMetadataProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/KotlinMetadataProcessor.kt
deleted file mode 100644
index 8ff1aa8..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/KotlinMetadataProcessor.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 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.ext
-
-import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
-import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
-import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
-import org.jetbrains.kotlin.serialization.ProtoBuf
-import javax.lang.model.element.ExecutableElement
-
-/**
- * Utility interface for processors that wants to run kotlin specific code.
- */
-interface KotlinMetadataProcessor : KotlinMetadataUtils {
-    /**
-     * Returns the parameter names of the function if all have names embedded in the metadata.
-     */
-    fun KotlinClassMetadata.getParameterNames(method: ExecutableElement): List<String>? {
-        val valueParameterList = this.data.getFunctionOrNull(method)?.valueParameterList
-                ?: findConstructor(method)?.valueParameterList
-                ?: return null
-        return if (valueParameterList.all { it.hasName() }) {
-            valueParameterList.map {
-                data.nameResolver.getName(it.name)
-                        .asString()
-                        .replace("`", "")
-                        .removeSuffix("?")
-                        .trim()
-            }
-        } else {
-            null
-        }
-    }
-
-    /**
-     * Finds the kotlin metadata for a constructor.
-     */
-    private fun KotlinClassMetadata.findConstructor(
-            executableElement: ExecutableElement
-    ): ProtoBuf.Constructor? {
-        val (nameResolver, classProto) = data
-        val jvmSignature = executableElement.jvmMethodSignature
-        // find constructor
-        return classProto.constructorList.singleOrNull {
-            it.getJvmConstructorSignature(nameResolver, classProto.typeTable) == jvmSignature
-        }
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/element_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/element_ext.kt
deleted file mode 100644
index fbd54c4..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/element_ext.kt
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-
-package android.arch.persistence.room.ext
-
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import org.jetbrains.kotlin.load.java.JvmAbi
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.AnnotationValue
-import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.TypeElement
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-import javax.lang.model.type.WildcardType
-import javax.lang.model.util.SimpleAnnotationValueVisitor6
-import javax.lang.model.util.SimpleTypeVisitor7
-import javax.lang.model.util.Types
-import kotlin.reflect.KClass
-
-fun Element.hasAnyOf(vararg modifiers: Modifier): Boolean {
-    return this.modifiers.any { modifiers.contains(it) }
-}
-
-fun Element.hasAnnotation(klass: KClass<out Annotation>): Boolean {
-    return MoreElements.isAnnotationPresent(this, klass.java)
-}
-
-fun Element.isNonNull() =
-        asType().kind.isPrimitive
-                || hasAnnotation(android.support.annotation.NonNull::class)
-                || hasAnnotation(org.jetbrains.annotations.NotNull::class)
-
-/**
- * gets all members including super privates. does not handle duplicate field names!!!
- */
-// TODO handle conflicts with super: b/35568142
-fun TypeElement.getAllFieldsIncludingPrivateSupers(processingEnvironment: ProcessingEnvironment):
-        Set<VariableElement> {
-    val myMembers = processingEnvironment.elementUtils.getAllMembers(this)
-            .filter { it.kind == ElementKind.FIELD }
-            .filter { it is VariableElement }
-            .map { it as VariableElement }
-            .toSet()
-    if (superclass.kind != TypeKind.NONE) {
-        return myMembers + MoreTypes.asTypeElement(superclass)
-                .getAllFieldsIncludingPrivateSupers(processingEnvironment)
-    } else {
-        return myMembers
-    }
-}
-
-// code below taken from dagger2
-// compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java
-private val TO_LIST_OF_TYPES = object
-    : SimpleAnnotationValueVisitor6<List<TypeMirror>, Void?>() {
-    override fun visitArray(values: MutableList<out AnnotationValue>?, p: Void?): List<TypeMirror> {
-        return values?.map {
-            val tmp = TO_TYPE.visit(it)
-            tmp
-        }?.filterNotNull() ?: emptyList()
-    }
-
-    override fun defaultAction(o: Any?, p: Void?): List<TypeMirror>? {
-        return emptyList()
-    }
-}
-
-private val TO_TYPE = object : SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
-
-    override fun visitType(t: TypeMirror, p: Void?): TypeMirror {
-        return t
-    }
-
-    override fun defaultAction(o: Any?, p: Void?): TypeMirror {
-        throw TypeNotPresentException(o!!.toString(), null)
-    }
-}
-
-fun AnnotationValue.toListOfClassTypes(): List<TypeMirror> {
-    return TO_LIST_OF_TYPES.visit(this)
-}
-
-fun AnnotationValue.toType(): TypeMirror {
-    return TO_TYPE.visit(this)
-}
-
-fun AnnotationValue.toClassType(): TypeMirror? {
-    return TO_TYPE.visit(this)
-}
-
-fun TypeMirror.isCollection(): Boolean {
-    return MoreTypes.isType(this)
-            && (MoreTypes.isTypeOf(java.util.List::class.java, this)
-            || MoreTypes.isTypeOf(java.util.Set::class.java, this))
-}
-
-fun Element.getAnnotationValue(annotation: Class<out Annotation>, fieldName: String): Any? {
-    return MoreElements.getAnnotationMirror(this, annotation)
-            .orNull()?.let {
-        AnnotationMirrors.getAnnotationValue(it, fieldName)?.value
-    }
-}
-
-private val ANNOTATION_VALUE_TO_INT_VISITOR = object : SimpleAnnotationValueVisitor6<Int?, Void>() {
-    override fun visitInt(i: Int, p: Void?): Int? {
-        return i
-    }
-}
-
-private val ANNOTATION_VALUE_TO_BOOLEAN_VISITOR = object
-    : SimpleAnnotationValueVisitor6<Boolean?, Void>() {
-    override fun visitBoolean(b: Boolean, p: Void?): Boolean? {
-        return b
-    }
-}
-
-private val ANNOTATION_VALUE_TO_STRING_VISITOR = object
-    : SimpleAnnotationValueVisitor6<String?, Void>() {
-    override fun visitString(s: String?, p: Void?): String? {
-        return s
-    }
-}
-
-private val ANNOTATION_VALUE_STRING_ARR_VISITOR = object
-    : SimpleAnnotationValueVisitor6<List<String>, Void>() {
-    override fun visitArray(vals: MutableList<out AnnotationValue>?, p: Void?): List<String> {
-        return vals?.map {
-            ANNOTATION_VALUE_TO_STRING_VISITOR.visit(it)
-        }?.filterNotNull() ?: emptyList()
-    }
-}
-
-fun AnnotationValue.getAsInt(def: Int? = null): Int? {
-    return ANNOTATION_VALUE_TO_INT_VISITOR.visit(this) ?: def
-}
-
-fun AnnotationValue.getAsString(def: String? = null): String? {
-    return ANNOTATION_VALUE_TO_STRING_VISITOR.visit(this) ?: def
-}
-
-fun AnnotationValue.getAsBoolean(def: Boolean): Boolean {
-    return ANNOTATION_VALUE_TO_BOOLEAN_VISITOR.visit(this) ?: def
-}
-
-fun AnnotationValue.getAsStringList(): List<String> {
-    return ANNOTATION_VALUE_STRING_ARR_VISITOR.visit(this)
-}
-
-// a variant of Types.isAssignable that ignores variance.
-fun Types.isAssignableWithoutVariance(from: TypeMirror, to: TypeMirror): Boolean {
-    val assignable = isAssignable(from, to)
-    if (assignable) {
-        return true
-    }
-    if (from.kind != TypeKind.DECLARED || to.kind != TypeKind.DECLARED) {
-        return false
-    }
-    val declaredFrom = MoreTypes.asDeclared(from)
-    val declaredTo = MoreTypes.asDeclared(to)
-    val fromTypeArgs = declaredFrom.typeArguments
-    val toTypeArgs = declaredTo.typeArguments
-    // no type arguments, we don't need extra checks
-    if (fromTypeArgs.isEmpty() || fromTypeArgs.size != toTypeArgs.size) {
-        return false
-    }
-    // check erasure version first, if it does not match, no reason to proceed
-    if (!isAssignable(erasure(from), erasure(to))) {
-        return false
-    }
-    // convert from args to their upper bounds if it exists
-    val fromExtendsBounds = fromTypeArgs.map {
-        it.extendsBound()
-    }
-    // if there are no upper bound conversions, return.
-    if (fromExtendsBounds.all { it == null }) {
-        return false
-    }
-    // try to move the types of the from to their upper bounds. It does not matter for the "to"
-    // because Types.isAssignable handles it as it is valid java
-    return (0 until fromTypeArgs.size).all { index ->
-        isAssignableWithoutVariance(
-                from = fromExtendsBounds[index] ?: fromTypeArgs[index],
-                to = toTypeArgs[index])
-    }
-}
-
-// converts ? in Set< ? extends Foo> to Foo
-fun TypeMirror.extendsBound(): TypeMirror? {
-    return this.accept(object : SimpleTypeVisitor7<TypeMirror?, Void?>() {
-        override fun visitWildcard(type: WildcardType, ignored: Void?): TypeMirror? {
-            return type.extendsBound
-        }
-    }, null)
-}
-
-/**
- * Finds the default implementation method corresponding to this Kotlin interface method.
- */
-fun Element.findKotlinDefaultImpl(typeUtils: Types): Element? {
-    fun paramsMatch(ourParams: List<VariableElement>, theirParams: List<VariableElement>): Boolean {
-        if (ourParams.size != theirParams.size - 1) {
-            return false
-        }
-        ourParams.forEachIndexed { i, variableElement ->
-            // Plus 1 to their index because their first param is a self object.
-            if (!typeUtils.isSameType(theirParams[i + 1].asType(), variableElement.asType())) {
-                return false
-            }
-        }
-        return true
-    }
-
-    val parent = this.enclosingElement as TypeElement
-    val innerClass = parent.enclosedElements.find {
-        it.kind == ElementKind.CLASS && it.simpleName.contentEquals(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)
-    } ?: return null
-    return innerClass.enclosedElements.find {
-        it.kind == ElementKind.METHOD && it.simpleName == this.simpleName
-                && paramsMatch(MoreElements.asExecutable(this).parameters,
-                MoreElements.asExecutable(it).parameters)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/javapoet_ext.kt
deleted file mode 100644
index fc6e693..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/javapoet_ext.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.ext
-
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-import javax.lang.model.type.TypeMirror
-import kotlin.reflect.KClass
-
-val L = "\$L"
-val T = "\$T"
-val N = "\$N"
-val S = "\$S"
-
-fun KClass<*>.typeName() = ClassName.get(this.java)
-fun KClass<*>.arrayTypeName() = ArrayTypeName.of(typeName())
-fun TypeMirror.typeName() = TypeName.get(this)
-
-object SupportDbTypeNames {
-    val DB: ClassName = ClassName.get("android.arch.persistence.db", "SupportSQLiteDatabase")
-    val SQLITE_STMT: ClassName =
-            ClassName.get("android.arch.persistence.db", "SupportSQLiteStatement")
-    val SQLITE_OPEN_HELPER: ClassName =
-            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper")
-    val SQLITE_OPEN_HELPER_CALLBACK: ClassName =
-            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper.Callback")
-    val SQLITE_OPEN_HELPER_FACTORY: ClassName =
-            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper.Factory")
-    val SQLITE_OPEN_HELPER_CONFIG: ClassName =
-            ClassName.get("android.arch.persistence.db", "SupportSQLiteOpenHelper.Configuration")
-    val SQLITE_OPEN_HELPER_CONFIG_BUILDER: ClassName =
-            ClassName.get("android.arch.persistence.db",
-                    "SupportSQLiteOpenHelper.Configuration.Builder")
-    val QUERY: ClassName =
-            ClassName.get("android.arch.persistence.db", "SupportSQLiteQuery")
-}
-
-object RoomTypeNames {
-    val STRING_UTIL: ClassName = ClassName.get("android.arch.persistence.room.util", "StringUtil")
-    val CURSOR_CONVERTER: ClassName =
-            ClassName.get("android.arch.persistence.room", "CursorConverter")
-    val ROOM: ClassName = ClassName.get("android.arch.persistence.room", "Room")
-    val ROOM_DB: ClassName = ClassName.get("android.arch.persistence.room", "RoomDatabase")
-    val ROOM_DB_CONFIG: ClassName = ClassName.get("android.arch.persistence.room",
-            "DatabaseConfiguration")
-    val INSERTION_ADAPTER: ClassName =
-            ClassName.get("android.arch.persistence.room", "EntityInsertionAdapter")
-    val DELETE_OR_UPDATE_ADAPTER: ClassName =
-            ClassName.get("android.arch.persistence.room", "EntityDeletionOrUpdateAdapter")
-    val SHARED_SQLITE_STMT: ClassName =
-            ClassName.get("android.arch.persistence.room", "SharedSQLiteStatement")
-    val INVALIDATION_TRACKER: ClassName =
-            ClassName.get("android.arch.persistence.room", "InvalidationTracker")
-    val INVALIDATION_OBSERVER: ClassName =
-            ClassName.get("android.arch.persistence.room.InvalidationTracker", "Observer")
-    val ROOM_SQL_QUERY: ClassName =
-            ClassName.get("android.arch.persistence.room", "RoomSQLiteQuery")
-    val OPEN_HELPER: ClassName =
-            ClassName.get("android.arch.persistence.room", "RoomOpenHelper")
-    val OPEN_HELPER_DELEGATE: ClassName =
-            ClassName.get("android.arch.persistence.room", "RoomOpenHelper.Delegate")
-    val TABLE_INFO: ClassName =
-            ClassName.get("android.arch.persistence.room.util", "TableInfo")
-    val TABLE_INFO_COLUMN: ClassName =
-            ClassName.get("android.arch.persistence.room.util", "TableInfo.Column")
-    val TABLE_INFO_FOREIGN_KEY: ClassName =
-            ClassName.get("android.arch.persistence.room.util", "TableInfo.ForeignKey")
-    val TABLE_INFO_INDEX: ClassName =
-            ClassName.get("android.arch.persistence.room.util", "TableInfo.Index")
-    val LIMIT_OFFSET_DATA_SOURCE: ClassName =
-            ClassName.get("android.arch.persistence.room.paging", "LimitOffsetDataSource")
-}
-
-object ArchTypeNames {
-    val APP_EXECUTOR: ClassName =
-            ClassName.get("android.arch.core.executor", "ArchTaskExecutor")
-}
-
-object PagingTypeNames {
-    val DATA_SOURCE: ClassName =
-            ClassName.get("android.arch.paging", "DataSource")
-    val POSITIONAL_DATA_SOURCE: ClassName =
-            ClassName.get("android.arch.paging", "PositionalDataSource")
-    val DATA_SOURCE_FACTORY: ClassName =
-            ClassName.get("android.arch.paging", "DataSource.Factory")
-}
-
-object LifecyclesTypeNames {
-    val LIVE_DATA: ClassName = ClassName.get("android.arch.lifecycle", "LiveData")
-    val COMPUTABLE_LIVE_DATA: ClassName = ClassName.get("android.arch.lifecycle",
-            "ComputableLiveData")
-}
-
-object AndroidTypeNames {
-    val CURSOR: ClassName = ClassName.get("android.database", "Cursor")
-    val ARRAY_MAP: ClassName = ClassName.get("android.support.v4.util", "ArrayMap")
-    val BUILD: ClassName = ClassName.get("android.os", "Build")
-}
-
-object CommonTypeNames {
-    val LIST = ClassName.get("java.util", "List")
-    val SET = ClassName.get("java.util", "Set")
-    val STRING = ClassName.get("java.lang", "String")
-    val INTEGER = ClassName.get("java.lang", "Integer")
-    val OPTIONAL = ClassName.get("java.util", "Optional")
-}
-
-object GuavaBaseTypeNames {
-    val OPTIONAL = ClassName.get("com.google.common.base", "Optional")
-}
-
-object GuavaUtilConcurrentTypeNames {
-    val LISTENABLE_FUTURE = ClassName.get("com.google.common.util.concurrent", "ListenableFuture")
-}
-
-object RxJava2TypeNames {
-    val FLOWABLE = ClassName.get("io.reactivex", "Flowable")
-    val MAYBE = ClassName.get("io.reactivex", "Maybe")
-    val SINGLE = ClassName.get("io.reactivex", "Single")
-}
-
-object ReactiveStreamsTypeNames {
-    val PUBLISHER = ClassName.get("org.reactivestreams", "Publisher")
-}
-
-object RoomGuavaTypeNames {
-    val GUAVA_ROOM = ClassName.get("android.arch.persistence.room.guava", "GuavaRoom")
-}
-
-object RoomRxJava2TypeNames {
-    val RX_ROOM = ClassName.get("android.arch.persistence.room", "RxRoom")
-    val RX_EMPTY_RESULT_SET_EXCEPTION = ClassName.get("android.arch.persistence.room",
-            "EmptyResultSetException")
-}
-
-fun TypeName.defaultValue(): String {
-    return if (!isPrimitive) {
-        "null"
-    } else if (this == TypeName.BOOLEAN) {
-        "false"
-    } else {
-        "0"
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/log/RLog.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/log/RLog.kt
deleted file mode 100644
index 9832996..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/log/RLog.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("unused")
-
-package android.arch.persistence.room.log
-
-import android.arch.persistence.room.vo.Warning
-import java.util.UnknownFormatConversionException
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Element
-import javax.tools.Diagnostic
-import javax.tools.Diagnostic.Kind.ERROR
-import javax.tools.Diagnostic.Kind.NOTE
-import javax.tools.Diagnostic.Kind.WARNING
-
-class RLog(val messager: Messager, val suppressedWarnings: Set<Warning>,
-           val defaultElement: Element?) {
-    private fun String.safeFormat(vararg args: Any): String {
-        try {
-            return format(args)
-        } catch (ex: UnknownFormatConversionException) {
-            // the input string might be from random source in which case we rather print the
-            // msg as is instead of crashing while reporting an error.
-            return this
-        }
-    }
-
-    fun d(element: Element, msg: String, vararg args: Any) {
-        messager.printMessage(NOTE, msg.safeFormat(args), element)
-    }
-
-    fun d(msg: String, vararg args: Any) {
-        messager.printMessage(NOTE, msg.safeFormat(args))
-    }
-
-    fun e(element: Element, msg: String, vararg args: Any) {
-        messager.printMessage(ERROR, msg.safeFormat(args), element)
-    }
-
-    fun e(msg: String, vararg args: Any) {
-        messager.printMessage(ERROR, msg.safeFormat(args), defaultElement)
-    }
-
-    fun w(warning: Warning, element: Element? = null, msg: String, vararg args: Any) {
-        if (suppressedWarnings.contains(warning)) {
-            return
-        }
-        messager.printMessage(WARNING, msg.safeFormat(args),
-                element ?: defaultElement)
-    }
-
-    fun w(warning: Warning, msg: String, vararg args: Any) {
-        if (suppressedWarnings.contains(warning)) {
-            return
-        }
-        messager.printMessage(WARNING, msg.safeFormat(args), defaultElement)
-    }
-
-    interface Messager {
-        fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element? = null)
-    }
-
-    class ProcessingEnvMessager(val processingEnv: ProcessingEnvironment) : Messager {
-        override fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element?) {
-            processingEnv.messager.printMessage(kind, msg, element)
-        }
-    }
-
-    class CollectingMessager : Messager {
-        private val messages = mutableMapOf<Diagnostic.Kind, MutableList<Pair<String, Element?>>> ()
-        override fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element?) {
-            messages.getOrPut(kind, {
-                arrayListOf<Pair<String, Element?>>()
-            }).add(Pair(msg, element))
-        }
-
-        fun hasErrors() = messages.containsKey(Diagnostic.Kind.ERROR)
-
-        fun writeTo(env: ProcessingEnvironment) {
-            messages.forEach { pair ->
-                val kind = pair.key
-                pair.value.forEach {
-                    env.messager.printMessage(kind, it.first, it.second)
-                }
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParsedQuery.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParsedQuery.kt
deleted file mode 100644
index 55a9bf9..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParsedQuery.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.parser
-
-import android.arch.persistence.room.parser.SectionType.BIND_VAR
-import android.arch.persistence.room.parser.SectionType.NEWLINE
-import android.arch.persistence.room.parser.SectionType.TEXT
-import android.arch.persistence.room.verifier.QueryResultInfo
-import org.antlr.v4.runtime.tree.TerminalNode
-
-enum class SectionType {
-    BIND_VAR,
-    TEXT,
-    NEWLINE
-}
-
-data class Section(val text: String, val type: SectionType) {
-    companion object {
-        fun text(text: String) = Section(text, SectionType.TEXT)
-        fun newline() = Section("", SectionType.NEWLINE)
-        fun bindVar(text: String) = Section(text, SectionType.BIND_VAR)
-    }
-}
-
-data class Table(val name: String, val alias: String)
-
-data class ParsedQuery(
-        val original: String,
-        val type: QueryType,
-        val inputs: List<TerminalNode>,
-        // pairs of table name and alias,
-        val tables: Set<Table>,
-        val syntaxErrors: List<String>,
-        val runtimeQueryPlaceholder: Boolean) {
-    companion object {
-        val STARTS_WITH_NUMBER = "^\\?[0-9]".toRegex()
-        val MISSING = ParsedQuery("missing query", QueryType.UNKNOWN, emptyList(), emptySet(),
-                emptyList(), false)
-    }
-
-    /**
-     * Optional data that might be assigned when the query is parsed inside an annotation processor.
-     * User may turn this off or it might be disabled for any reason so generated code should
-     * always handle not having it.
-     */
-    var resultInfo: QueryResultInfo? = null
-
-    val sections by lazy {
-        val lines = original.lines()
-        val inputsByLine = inputs.groupBy { it.symbol.line }
-        val sections = arrayListOf<Section>()
-        lines.forEachIndexed { index, line ->
-            var charInLine = 0
-            inputsByLine[index + 1]?.forEach { bindVar ->
-                if (charInLine < bindVar.symbol.charPositionInLine) {
-                    sections.add(Section.text(line.substring(charInLine,
-                            bindVar.symbol.charPositionInLine)))
-                }
-                sections.add(Section.bindVar(bindVar.text))
-                charInLine = bindVar.symbol.charPositionInLine + bindVar.symbol.text.length
-            }
-            if (charInLine < line.length) {
-                sections.add(Section.text(line.substring(charInLine)))
-            }
-            if (index + 1 < lines.size) {
-                sections.add(Section.newline())
-            }
-        }
-        sections
-    }
-
-    val bindSections by lazy { sections.filter { it.type == BIND_VAR } }
-
-    private fun unnamedVariableErrors(): List<String> {
-        val anonymousBindError = if (inputs.any { it.text == "?" }) {
-            arrayListOf(ParserErrors.ANONYMOUS_BIND_ARGUMENT)
-        } else {
-            emptyList<String>()
-        }
-        return anonymousBindError + inputs.filter {
-            it.text.matches(STARTS_WITH_NUMBER)
-        }.map {
-            ParserErrors.cannotUseVariableIndices(it.text, it.symbol.charPositionInLine)
-        }
-    }
-
-    private fun unknownQueryTypeErrors(): List<String> {
-        return if (QueryType.SUPPORTED.contains(type)) {
-            emptyList()
-        } else {
-            listOf(ParserErrors.invalidQueryType(type))
-        }
-    }
-
-    val errors by lazy {
-        if (syntaxErrors.isNotEmpty()) {
-            // if there is a syntax error, don't report others since they might be misleading.
-            syntaxErrors
-        } else {
-            unnamedVariableErrors() + unknownQueryTypeErrors()
-        }
-    }
-
-    val queryWithReplacedBindParams by lazy {
-        sections.joinToString("") {
-            when (it.type) {
-                TEXT -> it.text
-                BIND_VAR -> "?"
-                NEWLINE -> "\n"
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParserErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParserErrors.kt
deleted file mode 100644
index e3b4f40..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/ParserErrors.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.parser
-
-object ParserErrors {
-    val ANONYMOUS_BIND_ARGUMENT = "Room does not support ? as bind parameters. You must use" +
-            " named bind arguments (e..g :argName)"
-
-    val NOT_ONE_QUERY = "Must have exactly 1 query in @Query value"
-
-    fun invalidQueryType(type: QueryType): String {
-        return "$type query type is not supported yet. You can use:" +
-                QueryType.SUPPORTED.joinToString(", ") { it.name }
-    }
-
-    fun cannotUseVariableIndices(name: String, position: Int) = "Cannot use variable indices." +
-            " Use named parameters instead (e.g. WHERE name LIKE :nameArg and lastName LIKE " +
-            ":lastName). Problem: $name at $position"
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
deleted file mode 100644
index 0114a83..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/parser/SqlParser.kt
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.parser
-
-import android.arch.persistence.room.ColumnInfo
-import org.antlr.v4.runtime.ANTLRInputStream
-import org.antlr.v4.runtime.BaseErrorListener
-import org.antlr.v4.runtime.CommonTokenStream
-import org.antlr.v4.runtime.RecognitionException
-import org.antlr.v4.runtime.Recognizer
-import org.antlr.v4.runtime.tree.ParseTree
-import org.antlr.v4.runtime.tree.TerminalNode
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-
-@Suppress("FunctionName")
-class QueryVisitor(
-        private val original: String,
-        private val syntaxErrors: ArrayList<String>,
-        statement: ParseTree,
-        private val forRuntimeQuery: Boolean
-) : SQLiteBaseVisitor<Void?>() {
-    private val bindingExpressions = arrayListOf<TerminalNode>()
-    // table name alias mappings
-    private val tableNames = mutableSetOf<Table>()
-    private val withClauseNames = mutableSetOf<String>()
-    private val queryType: QueryType
-
-    init {
-        queryType = (0 until statement.childCount).map {
-            findQueryType(statement.getChild(it))
-        }.filterNot { it == QueryType.UNKNOWN }.firstOrNull() ?: QueryType.UNKNOWN
-
-        statement.accept(this)
-    }
-
-    private fun findQueryType(statement: ParseTree): QueryType {
-        return when (statement) {
-            is SQLiteParser.Factored_select_stmtContext,
-            is SQLiteParser.Compound_select_stmtContext,
-            is SQLiteParser.Select_stmtContext,
-            is SQLiteParser.Simple_select_stmtContext ->
-                QueryType.SELECT
-
-            is SQLiteParser.Delete_stmt_limitedContext,
-            is SQLiteParser.Delete_stmtContext ->
-                QueryType.DELETE
-
-            is SQLiteParser.Insert_stmtContext ->
-                QueryType.INSERT
-            is SQLiteParser.Update_stmtContext,
-            is SQLiteParser.Update_stmt_limitedContext ->
-                QueryType.UPDATE
-            is TerminalNode -> when (statement.text) {
-                "EXPLAIN" -> QueryType.EXPLAIN
-                else -> QueryType.UNKNOWN
-            }
-            else -> QueryType.UNKNOWN
-        }
-    }
-
-    override fun visitExpr(ctx: SQLiteParser.ExprContext): Void? {
-        val bindParameter = ctx.BIND_PARAMETER()
-        if (bindParameter != null) {
-            bindingExpressions.add(bindParameter)
-        }
-        return super.visitExpr(ctx)
-    }
-
-    fun createParsedQuery(): ParsedQuery {
-        return ParsedQuery(
-                original = original,
-                type = queryType,
-                inputs = bindingExpressions.sortedBy { it.sourceInterval.a },
-                tables = tableNames,
-                syntaxErrors = syntaxErrors,
-                runtimeQueryPlaceholder = forRuntimeQuery)
-    }
-
-    override fun visitCommon_table_expression(
-            ctx: SQLiteParser.Common_table_expressionContext): Void? {
-        val tableName = ctx.table_name()?.text
-        if (tableName != null) {
-            withClauseNames.add(unescapeIdentifier(tableName))
-        }
-        return super.visitCommon_table_expression(ctx)
-    }
-
-    override fun visitTable_or_subquery(ctx: SQLiteParser.Table_or_subqueryContext): Void? {
-        val tableName = ctx.table_name()?.text
-        if (tableName != null) {
-            val tableAlias = ctx.table_alias()?.text
-            if (tableName !in withClauseNames) {
-                tableNames.add(Table(
-                        unescapeIdentifier(tableName),
-                        unescapeIdentifier(tableAlias ?: tableName)))
-            }
-        }
-        return super.visitTable_or_subquery(ctx)
-    }
-
-    private fun unescapeIdentifier(text: String): String {
-        val trimmed = text.trim()
-        ESCAPE_LITERALS.forEach {
-            if (trimmed.startsWith(it) && trimmed.endsWith(it)) {
-                return unescapeIdentifier(trimmed.substring(1, trimmed.length - 1))
-            }
-        }
-        return trimmed
-    }
-
-    companion object {
-        private val ESCAPE_LITERALS = listOf("\"", "'", "`")
-    }
-}
-
-class SqlParser {
-    companion object {
-        private val INVALID_IDENTIFIER_CHARS = arrayOf('`', '\"')
-        fun parse(input: String): ParsedQuery {
-            val inputStream = ANTLRInputStream(input)
-            val lexer = SQLiteLexer(inputStream)
-            val tokenStream = CommonTokenStream(lexer)
-            val parser = SQLiteParser(tokenStream)
-            val syntaxErrors = arrayListOf<String>()
-            parser.addErrorListener(object : BaseErrorListener() {
-                override fun syntaxError(
-                        recognizer: Recognizer<*, *>, offendingSymbol: Any,
-                        line: Int, charPositionInLine: Int, msg: String,
-                        e: RecognitionException?) {
-                    syntaxErrors.add(msg)
-                }
-            })
-            try {
-                val parsed = parser.parse()
-                val statementList = parsed.sql_stmt_list()
-                if (statementList.isEmpty()) {
-                    syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
-                    return ParsedQuery(input, QueryType.UNKNOWN, emptyList(), emptySet(),
-                            listOf(ParserErrors.NOT_ONE_QUERY), false)
-                }
-                val statements = statementList.first().children
-                        .filter { it is SQLiteParser.Sql_stmtContext }
-                if (statements.size != 1) {
-                    syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
-                }
-                val statement = statements.first()
-                return QueryVisitor(
-                        original = input,
-                        syntaxErrors = syntaxErrors,
-                        statement = statement,
-                        forRuntimeQuery = false).createParsedQuery()
-            } catch (antlrError: RuntimeException) {
-                return ParsedQuery(input, QueryType.UNKNOWN, emptyList(), emptySet(),
-                        listOf("unknown error while parsing $input : ${antlrError.message}"),
-                        false)
-            }
-        }
-
-        fun isValidIdentifier(input: String): Boolean =
-                input.isNotBlank() && INVALID_IDENTIFIER_CHARS.none { input.contains(it) }
-
-        /**
-         * creates a dummy select query for raw queries that queries the given list of tables.
-         */
-        fun rawQueryForTables(tableNames: Set<String>): ParsedQuery {
-            return ParsedQuery(
-                    original = "raw query",
-                    type = QueryType.UNKNOWN,
-                    inputs = emptyList(),
-                    tables = tableNames.map { Table(name = it, alias = it) }.toSet(),
-                    syntaxErrors = emptyList(),
-                    runtimeQueryPlaceholder = true
-            )
-        }
-    }
-}
-
-enum class QueryType {
-    UNKNOWN,
-    SELECT,
-    DELETE,
-    UPDATE,
-    EXPLAIN,
-    INSERT;
-
-    companion object {
-        // IF you change this, don't forget to update @Query documentation.
-        val SUPPORTED = hashSetOf(SELECT, DELETE, UPDATE)
-    }
-}
-
-enum class SQLTypeAffinity {
-    NULL,
-    TEXT,
-    INTEGER,
-    REAL,
-    BLOB;
-
-    fun getTypeMirrors(env: ProcessingEnvironment): List<TypeMirror>? {
-        val typeUtils = env.typeUtils
-        return when (this) {
-            TEXT -> listOf(env.elementUtils.getTypeElement("java.lang.String").asType())
-            INTEGER -> withBoxedTypes(env, TypeKind.INT, TypeKind.BYTE, TypeKind.CHAR,
-                    TypeKind.LONG, TypeKind.SHORT)
-            REAL -> withBoxedTypes(env, TypeKind.DOUBLE, TypeKind.FLOAT)
-            BLOB -> listOf(typeUtils.getArrayType(
-                    typeUtils.getPrimitiveType(TypeKind.BYTE)))
-            else -> emptyList()
-        }
-    }
-
-    private fun withBoxedTypes(env: ProcessingEnvironment, vararg primitives: TypeKind):
-            List<TypeMirror> {
-        return primitives.flatMap {
-            val primitiveType = env.typeUtils.getPrimitiveType(it)
-            listOf(primitiveType, env.typeUtils.boxedClass(primitiveType).asType())
-        }
-    }
-
-    companion object {
-        // converts from ColumnInfo#SQLiteTypeAffinity
-        fun fromAnnotationValue(value: Int): SQLTypeAffinity? {
-            return when (value) {
-                ColumnInfo.BLOB -> BLOB
-                ColumnInfo.INTEGER -> INTEGER
-                ColumnInfo.REAL -> REAL
-                ColumnInfo.TEXT -> TEXT
-                else -> null
-            }
-        }
-    }
-}
-
-enum class Collate {
-    BINARY,
-    NOCASE,
-    RTRIM,
-    LOCALIZED,
-    UNICODE;
-
-    companion object {
-        fun fromAnnotationValue(value: Int): Collate? {
-            return when (value) {
-                ColumnInfo.BINARY -> BINARY
-                ColumnInfo.NOCASE -> NOCASE
-                ColumnInfo.RTRIM -> RTRIM
-                ColumnInfo.LOCALIZED -> LOCALIZED
-                ColumnInfo.UNICODE -> UNICODE
-                else -> null
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/preconditions/Checks.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/preconditions/Checks.kt
deleted file mode 100644
index 588ce94..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/preconditions/Checks.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.preconditions
-
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.log.RLog
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeVariableName
-import javax.lang.model.element.Element
-import kotlin.reflect.KClass
-
-/**
- * Similar to preconditions but element bound and just logs the error instead of throwing an
- * exception.
- * <p>
- * It is important for processing to continue when some errors happen so that we can generate as
- * much code as possible, leaving only the errors in javac output.
- */
-class Checks(private val logger: RLog) {
-
-    fun check(predicate: Boolean, element: Element, errorMsg: String, vararg args: Any): Boolean {
-        if (!predicate) {
-            logger.e(element, errorMsg, args)
-        }
-        return predicate
-    }
-
-    fun hasAnnotation(element: Element, annotation: KClass<out Annotation>, errorMsg: String,
-                      vararg args: Any): Boolean {
-        return if (!element.hasAnnotation(annotation)) {
-            logger.e(element, errorMsg, args)
-            false
-        } else {
-            true
-        }
-    }
-
-    fun notUnbound(typeName: TypeName, element: Element, errorMsg: String,
-                   vararg args: Any): Boolean {
-        // TODO support bounds cases like <T extends Foo> T bar()
-        val failed = check(typeName !is TypeVariableName, element, errorMsg, args)
-        if (typeName is ParameterizedTypeName) {
-            val nestedFailure = typeName.typeArguments
-                    .map { notUnbound(it, element, errorMsg, args) }
-                    .any { it == true }
-            return !(failed || nestedFailure)
-        }
-        return !failed
-    }
-
-    fun notBlank(value: String?, element: Element, msg: String, vararg args: Any): Boolean {
-        return check(value != null && value.isNotBlank(), element, msg, args)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/Context.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/Context.kt
deleted file mode 100644
index def5107..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/Context.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.log.RLog
-import android.arch.persistence.room.preconditions.Checks
-import android.arch.persistence.room.processor.cache.Cache
-import android.arch.persistence.room.solver.TypeAdapterStore
-import android.arch.persistence.room.verifier.DatabaseVerifier
-import java.io.File
-import java.util.LinkedHashSet
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Element
-import javax.lang.model.type.TypeMirror
-
-class Context private constructor(
-        val processingEnv: ProcessingEnvironment,
-        val logger: RLog,
-        private val typeConverters: CustomConverterProcessor.ProcessResult,
-        private val inheritedAdapterStore: TypeAdapterStore?,
-        val cache: Cache) {
-    val checker: Checks = Checks(logger)
-    val COMMON_TYPES: Context.CommonTypes = Context.CommonTypes(processingEnv)
-
-    val typeAdapterStore by lazy {
-        if (inheritedAdapterStore != null) {
-            TypeAdapterStore.copy(this, inheritedAdapterStore)
-        } else {
-            TypeAdapterStore.create(this, typeConverters.converters)
-        }
-    }
-
-    // set when database and its entities are processed.
-    var databaseVerifier: DatabaseVerifier? = null
-
-    companion object {
-        val ARG_OPTIONS by lazy {
-            ProcessorOptions.values().map { it.argName }
-        }
-    }
-
-    constructor(processingEnv: ProcessingEnvironment) : this(
-            processingEnv = processingEnv,
-            logger = RLog(RLog.ProcessingEnvMessager(processingEnv), emptySet(), null),
-            typeConverters = CustomConverterProcessor.ProcessResult.EMPTY,
-            inheritedAdapterStore = null,
-            cache = Cache(null, LinkedHashSet(), emptySet())) {
-    }
-
-    class CommonTypes(val processingEnv: ProcessingEnvironment) {
-        val STRING: TypeMirror by lazy {
-            processingEnv.elementUtils.getTypeElement("java.lang.String").asType()
-        }
-    }
-
-    val schemaOutFolder by lazy {
-        val arg = processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
-        if (arg?.isNotEmpty() ?: false) {
-            File(arg)
-        } else {
-            null
-        }
-    }
-
-    fun <T> collectLogs(handler: (Context) -> T): Pair<T, RLog.CollectingMessager> {
-        val collector = RLog.CollectingMessager()
-        val subContext = Context(processingEnv = processingEnv,
-                logger = RLog(collector, logger.suppressedWarnings, logger.defaultElement),
-                typeConverters = this.typeConverters,
-                inheritedAdapterStore = typeAdapterStore,
-                cache = cache)
-        subContext.databaseVerifier = databaseVerifier
-        val result = handler(subContext)
-        return Pair(result, collector)
-    }
-
-    fun fork(element: Element): Context {
-        val suppressedWarnings = SuppressWarningProcessor.getSuppressedWarnings(element)
-        val processConvertersResult = CustomConverterProcessor.findConverters(this, element)
-        val canReUseAdapterStore = processConvertersResult.classes.isEmpty()
-        // order here is important since the sub context should give priority to new converters.
-        val subTypeConverters = if (canReUseAdapterStore) {
-            this.typeConverters
-        } else {
-            processConvertersResult + this.typeConverters
-        }
-        val subSuppressedWarnings = suppressedWarnings + logger.suppressedWarnings
-        val subCache = Cache(cache, subTypeConverters.classes, subSuppressedWarnings)
-        val subContext = Context(
-                processingEnv = processingEnv,
-                logger = RLog(logger.messager, subSuppressedWarnings, element),
-                typeConverters = subTypeConverters,
-                inheritedAdapterStore = if (canReUseAdapterStore) typeAdapterStore else null,
-                cache = subCache)
-        subContext.databaseVerifier = databaseVerifier
-        return subContext
-    }
-
-    enum class ProcessorOptions(val argName: String) {
-        OPTION_SCHEMA_FOLDER("room.schemaLocation")
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/CustomConverterProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/CustomConverterProcessor.kt
deleted file mode 100644
index 622621d..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/CustomConverterProcessor.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.TypeConverter
-import android.arch.persistence.room.TypeConverters
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.hasAnyOf
-import android.arch.persistence.room.ext.toListOfClassTypes
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_BAD_RETURN_TYPE
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
-import android.arch.persistence.room.processor.ProcessorErrors
-        .TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_RECEIVE_1_PARAM
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
-import android.arch.persistence.room.solver.types.CustomTypeConverterWrapper
-import android.arch.persistence.room.vo.CustomTypeConverter
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import java.util.LinkedHashSet
-import javax.lang.model.element.Element
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-import javax.lang.model.util.ElementFilter
-
-/**
- * Processes classes that are referenced in TypeConverters annotations.
- */
-class CustomConverterProcessor(val context: Context, val element: TypeElement) {
-    companion object {
-        private val INVALID_RETURN_TYPES = setOf(TypeKind.ERROR, TypeKind.VOID, TypeKind.NONE)
-        fun findConverters(context: Context, element: Element): ProcessResult {
-            val annotation = MoreElements.getAnnotationMirror(element,
-                    TypeConverters::class.java).orNull()
-            return annotation?.let {
-                val classes = AnnotationMirrors.getAnnotationValue(annotation, "value")
-                        ?.toListOfClassTypes()
-                        ?.filter {
-                            MoreTypes.isType(it)
-                        }?.mapTo(LinkedHashSet(), { it }) ?: LinkedHashSet<TypeMirror>()
-                val converters = classes
-                        .flatMap {
-                            CustomConverterProcessor(context, MoreTypes.asTypeElement(it))
-                                    .process()
-                        }
-                converters.let {
-                    reportDuplicates(context, converters)
-                }
-                ProcessResult(classes, converters.map(::CustomTypeConverterWrapper))
-            } ?: ProcessResult.EMPTY
-        }
-
-        fun reportDuplicates(context: Context, converters: List<CustomTypeConverter>) {
-            val groupedByFrom = converters.groupBy { it.from.typeName() }
-            groupedByFrom.forEach {
-                it.value.groupBy { it.to.typeName() }.forEach {
-                    if (it.value.size > 1) {
-                        it.value.forEach { converter ->
-                            context.logger.e(converter.method, ProcessorErrors
-                                    .duplicateTypeConverters(it.value.minus(converter)))
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    fun process(): List<CustomTypeConverter> {
-        // using element utils instead of MoreElements to include statics.
-        val methods = ElementFilter
-                .methodsIn(context.processingEnv.elementUtils.getAllMembers(element))
-        val declaredType = MoreTypes.asDeclared(element.asType())
-        val converterMethods = methods.filter {
-            it.hasAnnotation(TypeConverter::class)
-        }
-        context.checker.check(converterMethods.isNotEmpty(), element, TYPE_CONVERTER_EMPTY_CLASS)
-        val allStatic = converterMethods.all { it.modifiers.contains(Modifier.STATIC) }
-        val constructors = ElementFilter.constructorsIn(
-                context.processingEnv.elementUtils.getAllMembers(element))
-        context.checker.check(allStatic || constructors.isEmpty() || constructors.any {
-            it.parameters.isEmpty()
-        }, element, TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
-        return converterMethods.map {
-            processMethod(declaredType, it)
-        }.filterNotNull()
-    }
-
-    private fun processMethod(
-            container: DeclaredType, methodElement: ExecutableElement): CustomTypeConverter? {
-        val asMember = context.processingEnv.typeUtils.asMemberOf(container, methodElement)
-        val executableType = MoreTypes.asExecutable(asMember)
-        val returnType = executableType.returnType
-        val invalidReturnType = INVALID_RETURN_TYPES.contains(returnType.kind)
-        context.checker.check(methodElement.hasAnyOf(Modifier.PUBLIC), methodElement,
-                TYPE_CONVERTER_MUST_BE_PUBLIC)
-        if (invalidReturnType) {
-            context.logger.e(methodElement, TYPE_CONVERTER_BAD_RETURN_TYPE)
-            return null
-        }
-        val returnTypeName = returnType.typeName()
-        context.checker.notUnbound(returnTypeName, methodElement,
-                TYPE_CONVERTER_UNBOUND_GENERIC)
-        val params = methodElement.parameters
-        if (params.size != 1) {
-            context.logger.e(methodElement, TYPE_CONVERTER_MUST_RECEIVE_1_PARAM)
-            return null
-        }
-        val param = params.map {
-            MoreTypes.asMemberOf(context.processingEnv.typeUtils, container, it)
-        }.first()
-        context.checker.notUnbound(param.typeName(), params[0], TYPE_CONVERTER_UNBOUND_GENERIC)
-        return CustomTypeConverter(container, methodElement, param, returnType)
-    }
-
-    /**
-     * Order of classes is important hence they are a LinkedHashSet not a set.
-     */
-    open class ProcessResult(
-            val classes: LinkedHashSet<TypeMirror>,
-            val converters: List<CustomTypeConverterWrapper>
-    ) {
-        object EMPTY : ProcessResult(LinkedHashSet(), emptyList())
-        operator fun plus(other: ProcessResult): ProcessResult {
-            val newClasses = LinkedHashSet<TypeMirror>()
-            newClasses.addAll(classes)
-            newClasses.addAll(other.classes)
-            return ProcessResult(newClasses, converters + other.converters)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
deleted file mode 100644
index 0a622c9..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DaoProcessor.kt
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Delete
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.RawQuery
-import android.arch.persistence.room.SkipQueryVerification
-import android.arch.persistence.room.Transaction
-import android.arch.persistence.room.Update
-import android.arch.persistence.room.ext.findKotlinDefaultImpl
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.hasAnyOf
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.verifier.DatabaseVerifier
-import android.arch.persistence.room.vo.Dao
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier.ABSTRACT
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-
-class DaoProcessor(baseContext: Context, val element: TypeElement, val dbType: DeclaredType,
-                   val dbVerifier: DatabaseVerifier?) {
-    val context = baseContext.fork(element)
-
-    companion object {
-        val PROCESSED_ANNOTATIONS = listOf(Insert::class, Delete::class, Query::class,
-                Update::class, RawQuery::class)
-    }
-
-    fun process(): Dao {
-        context.checker.hasAnnotation(element, android.arch.persistence.room.Dao::class,
-                ProcessorErrors.DAO_MUST_BE_ANNOTATED_WITH_DAO)
-        context.checker.check(element.hasAnyOf(ABSTRACT) || element.kind == ElementKind.INTERFACE,
-                element, ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
-
-        val declaredType = MoreTypes.asDeclared(element.asType())
-        val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
-        val methods = allMembers
-            .filter {
-                it.hasAnyOf(ABSTRACT) && it.kind == ElementKind.METHOD
-                        && it.findKotlinDefaultImpl(context.processingEnv.typeUtils) == null
-            }.map {
-                MoreElements.asExecutable(it)
-            }.groupBy { method ->
-                context.checker.check(
-                        PROCESSED_ANNOTATIONS.count { method.hasAnnotation(it) } == 1, method,
-                        ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD
-                )
-                if (method.hasAnnotation(Query::class)) {
-                    Query::class
-                } else if (method.hasAnnotation(Insert::class)) {
-                    Insert::class
-                } else if (method.hasAnnotation(Delete::class)) {
-                    Delete::class
-                } else if (method.hasAnnotation(Update::class)) {
-                    Update::class
-                } else if (method.hasAnnotation(RawQuery::class)) {
-                    RawQuery::class
-                } else {
-                    Any::class
-                }
-            }
-        val processorVerifier = if (element.hasAnnotation(SkipQueryVerification::class) ||
-                element.hasAnnotation(RawQuery::class)) {
-            null
-        } else {
-            dbVerifier
-        }
-
-        val queryMethods = methods[Query::class]?.map {
-            QueryMethodProcessor(
-                    baseContext = context,
-                    containing = declaredType,
-                    executableElement = it,
-                    dbVerifier = processorVerifier).process()
-        } ?: emptyList()
-
-        val rawQueryMethods = methods[RawQuery::class]?.map {
-            RawQueryMethodProcessor(
-                    baseContext = context,
-                    containing = declaredType,
-                    executableElement = it
-            ).process()
-        } ?: emptyList()
-
-        val insertionMethods = methods[Insert::class]?.map {
-            InsertionMethodProcessor(
-                    baseContext = context,
-                    containing = declaredType,
-                    executableElement = it).process()
-        } ?: emptyList()
-
-        val deletionMethods = methods[Delete::class]?.map {
-            DeletionMethodProcessor(
-                    baseContext = context,
-                    containing = declaredType,
-                    executableElement = it).process()
-        } ?: emptyList()
-
-        val updateMethods = methods[Update::class]?.map {
-            UpdateMethodProcessor(
-                    baseContext = context,
-                    containing = declaredType,
-                    executableElement = it).process()
-        } ?: emptyList()
-
-        val transactionMethods = allMembers.filter { member ->
-            member.hasAnnotation(Transaction::class)
-                    && member.kind == ElementKind.METHOD
-                    && PROCESSED_ANNOTATIONS.none { member.hasAnnotation(it) }
-        }.map {
-            TransactionMethodProcessor(
-                    baseContext = context,
-                    containing = declaredType,
-                    executableElement = MoreElements.asExecutable(it)).process()
-        }
-
-        val constructors = allMembers
-                .filter { it.kind == ElementKind.CONSTRUCTOR }
-                .map { MoreElements.asExecutable(it) }
-        val typeUtils = context.processingEnv.typeUtils
-        val goodConstructor = constructors
-                .filter {
-                    it.parameters.size == 1
-                            && typeUtils.isAssignable(dbType, it.parameters[0].asType())
-                }
-                .firstOrNull()
-        val constructorParamType = if (goodConstructor != null) {
-            goodConstructor.parameters[0].asType().typeName()
-        } else {
-            validateEmptyConstructor(constructors)
-            null
-        }
-
-        context.checker.check(methods[Any::class] == null, element,
-                ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION)
-
-        val type = TypeName.get(declaredType)
-        context.checker.notUnbound(type, element,
-                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES)
-
-        return Dao(element = element,
-                type = declaredType,
-                queryMethods = queryMethods,
-                rawQueryMethods = rawQueryMethods,
-                insertionMethods = insertionMethods,
-                deletionMethods = deletionMethods,
-                updateMethods = updateMethods,
-                transactionMethods = transactionMethods,
-                constructorParamType = constructorParamType)
-    }
-
-    private fun validateEmptyConstructor(constructors: List<ExecutableElement>) {
-        if (constructors.isNotEmpty() && constructors.all { it.parameters.isNotEmpty() }) {
-            context.logger.e(element, ProcessorErrors.daoMustHaveMatchingConstructor(
-                    element.toString(), dbType.toString()))
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DatabaseProcessor.kt
deleted file mode 100644
index 30acbf4..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DatabaseProcessor.kt
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.SkipQueryVerification
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.getAsBoolean
-import android.arch.persistence.room.ext.getAsInt
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.hasAnyOf
-import android.arch.persistence.room.ext.toListOfClassTypes
-import android.arch.persistence.room.verifier.DatabaseVerifier
-import android.arch.persistence.room.vo.Dao
-import android.arch.persistence.room.vo.DaoMethod
-import android.arch.persistence.room.vo.Database
-import android.arch.persistence.room.vo.Entity
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.AnnotationMirror
-import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.TypeMirror
-
-class DatabaseProcessor(baseContext: Context, val element: TypeElement) {
-    val context = baseContext.fork(element)
-
-    val baseClassElement: TypeMirror by lazy {
-        context.processingEnv.elementUtils.getTypeElement(
-                RoomTypeNames.ROOM_DB.packageName() + "." + RoomTypeNames.ROOM_DB.simpleName())
-                .asType()
-    }
-
-    fun process(): Database {
-        try {
-            return doProcess()
-        } finally {
-            context.databaseVerifier?.closeConnection(context)
-        }
-    }
-
-    private fun doProcess(): Database {
-        val dbAnnotation = MoreElements
-                .getAnnotationMirror(element, android.arch.persistence.room.Database::class.java)
-                .orNull()
-        val entities = processEntities(dbAnnotation, element)
-        validateUniqueTableNames(element, entities)
-        validateForeignKeys(element, entities)
-
-        val extendsRoomDb = context.processingEnv.typeUtils.isAssignable(
-                MoreElements.asType(element).asType(), baseClassElement)
-        context.checker.check(extendsRoomDb, element, ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
-
-        val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
-
-        val dbVerifier = if (element.hasAnnotation(SkipQueryVerification::class)) {
-            null
-        } else {
-            DatabaseVerifier.create(context, element, entities)
-        }
-        context.databaseVerifier = dbVerifier
-
-        val declaredType = MoreTypes.asDeclared(element.asType())
-        val daoMethods = allMembers.filter {
-            it.hasAnyOf(Modifier.ABSTRACT) && it.kind == ElementKind.METHOD
-        }.filterNot {
-            // remove methods that belong to room
-            val containing = it.enclosingElement
-            MoreElements.isType(containing) &&
-                    TypeName.get(containing.asType()) == RoomTypeNames.ROOM_DB
-        }.map {
-            val executable = MoreElements.asExecutable(it)
-            // TODO when we add support for non Dao return types (e.g. database), this code needs
-            // to change
-            val daoType = MoreTypes.asTypeElement(executable.returnType)
-            val dao = DaoProcessor(context, daoType, declaredType, dbVerifier).process()
-            DaoMethod(executable, executable.simpleName.toString(), dao)
-        }
-        validateUniqueDaoClasses(element, daoMethods, entities)
-        validateUniqueIndices(element, entities)
-        val version = AnnotationMirrors.getAnnotationValue(dbAnnotation, "version")
-                .getAsInt(1)!!.toInt()
-        val exportSchema = AnnotationMirrors.getAnnotationValue(dbAnnotation, "exportSchema")
-                .getAsBoolean(true)
-
-        val hasForeignKeys = entities.any { it.foreignKeys.isNotEmpty() }
-
-        val database = Database(
-                version = version,
-                element = element,
-                type = MoreElements.asType(element).asType(),
-                entities = entities,
-                daoMethods = daoMethods,
-                exportSchema = exportSchema,
-                enableForeignKeys = hasForeignKeys)
-        return database
-    }
-
-    private fun validateForeignKeys(element: TypeElement, entities: List<Entity>) {
-        val byTableName = entities.associateBy { it.tableName }
-        entities.forEach { entity ->
-            entity.foreignKeys.forEach foreignKeyLoop@ { foreignKey ->
-                val parent = byTableName[foreignKey.parentTable]
-                if (parent == null) {
-                    context.logger.e(element, ProcessorErrors
-                            .foreignKeyMissingParentEntityInDatabase(foreignKey.parentTable,
-                                    entity.element.qualifiedName.toString()))
-                    return@foreignKeyLoop
-                }
-                val parentFields = foreignKey.parentColumns.map { columnName ->
-                    val parentField = parent.fields.find {
-                        it.columnName == columnName
-                    }
-                    if (parentField == null) {
-                        context.logger.e(entity.element,
-                                ProcessorErrors.foreignKeyParentColumnDoesNotExist(
-                                        parentEntity = parent.element.qualifiedName.toString(),
-                                        missingColumn = columnName,
-                                        allColumns = parent.fields.map { it.columnName }))
-                    }
-                    parentField
-                }.filterNotNull()
-                if (parentFields.size != foreignKey.parentColumns.size) {
-                    return@foreignKeyLoop
-                }
-                // ensure that it is indexed in the parent
-                if (!parent.isUnique(foreignKey.parentColumns)) {
-                    context.logger.e(parent.element, ProcessorErrors
-                            .foreignKeyMissingIndexInParent(
-                                    parentEntity = parent.element.qualifiedName.toString(),
-                                    childEntity = entity.element.qualifiedName.toString(),
-                                    parentColumns = foreignKey.parentColumns,
-                                    childColumns = foreignKey.childFields
-                                            .map { it.columnName }))
-                    return@foreignKeyLoop
-                }
-            }
-        }
-    }
-
-    private fun validateUniqueIndices(element: TypeElement, entities: List<Entity>) {
-        entities
-                .flatMap { entity ->
-                    // associate each index with its entity
-                    entity.indices.map { Pair(it.name, entity) }
-                }
-                .groupBy { it.first } // group by index name
-                .filter { it.value.size > 1 } // get the ones with duplicate names
-                .forEach {
-                    // do not report duplicates from the same entity
-                    if (it.value.distinctBy { it.second.typeName }.size > 1) {
-                        context.logger.e(element,
-                                ProcessorErrors.duplicateIndexInDatabase(it.key,
-                                        it.value.map { "${it.second.typeName} > ${it.first}" }))
-                    }
-                }
-    }
-
-    private fun validateUniqueDaoClasses(dbElement: TypeElement, daoMethods: List<DaoMethod>,
-                                         entities: List<Entity>) {
-        val entityTypeNames = entities.map { it.typeName }.toSet()
-        daoMethods.groupBy { it.dao.typeName }
-                .forEach {
-                    if (it.value.size > 1) {
-                        val error = ProcessorErrors.duplicateDao(it.key, it.value.map { it.name })
-                        it.value.forEach { daoMethod ->
-                            context.logger.e(daoMethod.element,
-                                    ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
-                        }
-                        // also report the full error for the database
-                        context.logger.e(dbElement, error)
-                    }
-                }
-        val check = fun(element: Element, dao: Dao,
-                        typeName: TypeName?) {
-            typeName?.let {
-                if (!entityTypeNames.contains(typeName)) {
-                    context.logger.e(element,
-                            ProcessorErrors.shortcutEntityIsNotInDatabase(
-                                    database = dbElement.qualifiedName.toString(),
-                                    dao = dao.typeName.toString(),
-                                    entity = typeName.toString()
-                            ))
-                }
-            }
-        }
-        daoMethods.forEach { daoMethod ->
-            daoMethod.dao.shortcutMethods.forEach { method ->
-                method.entities.forEach {
-                    check(method.element, daoMethod.dao, it.value.typeName)
-                }
-            }
-            daoMethod.dao.insertionMethods.forEach { method ->
-                method.entities.forEach {
-                    check(method.element, daoMethod.dao, it.value.typeName)
-                }
-            }
-        }
-    }
-
-    private fun validateUniqueTableNames(dbElement: TypeElement, entities: List<Entity>) {
-        entities
-                .groupBy {
-                    it.tableName.toLowerCase()
-                }.filter {
-            it.value.size > 1
-        }.forEach { byTableName ->
-            val error = ProcessorErrors.duplicateTableNames(byTableName.key,
-                    byTableName.value.map { it.typeName.toString() })
-            // report it for each of them and the database to make it easier
-            // for the developer
-            byTableName.value.forEach { entity ->
-                context.logger.e(entity.element, error)
-            }
-            context.logger.e(dbElement, error)
-        }
-    }
-
-    private fun processEntities(dbAnnotation: AnnotationMirror?, element: TypeElement):
-            List<Entity> {
-        if (!context.checker.check(dbAnnotation != null, element,
-                ProcessorErrors.DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE)) {
-            return listOf()
-        }
-
-        val entityList = AnnotationMirrors.getAnnotationValue(dbAnnotation, "entities")
-        val listOfTypes = entityList.toListOfClassTypes()
-        context.checker.check(listOfTypes.isNotEmpty(), element,
-                ProcessorErrors.DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES)
-        return listOfTypes.map {
-            EntityProcessor(context, MoreTypes.asTypeElement(it)).process()
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessor.kt
deleted file mode 100644
index 4812306..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessor.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Delete
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.vo.DeletionMethod
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-
-class DeletionMethodProcessor(baseContext: Context,
-                              val containing: DeclaredType,
-                              val executableElement: ExecutableElement) {
-    val context = baseContext.fork(executableElement)
-
-    fun process(): DeletionMethod {
-        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
-        delegate.extractAnnotation(Delete::class, ProcessorErrors.MISSING_DELETE_ANNOTATION)
-
-        val returnTypeName = delegate.extractReturnType().typeName()
-        context.checker.check(
-                returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
-                executableElement,
-                ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
-        )
-
-        val (entities, params) = delegate.extractParams(
-                missingParamError = ProcessorErrors
-                        .DELETION_MISSING_PARAMS
-        )
-
-        return DeletionMethod(
-                element = delegate.executableElement,
-                name = delegate.executableElement.simpleName.toString(),
-                entities = entities,
-                returnCount = returnTypeName == TypeName.INT,
-                parameters = params
-        )
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
deleted file mode 100644
index 3859028..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/EntityProcessor.kt
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.ext.getAsBoolean
-import android.arch.persistence.room.ext.getAsInt
-import android.arch.persistence.room.ext.getAsString
-import android.arch.persistence.room.ext.getAsStringList
-import android.arch.persistence.room.ext.toType
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.parser.SqlParser
-import android.arch.persistence.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
-import android.arch.persistence.room.processor.ProcessorErrors.RELATION_IN_ENTITY
-import android.arch.persistence.room.processor.cache.Cache
-import android.arch.persistence.room.vo.EmbeddedField
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.ForeignKey
-import android.arch.persistence.room.vo.ForeignKeyAction
-import android.arch.persistence.room.vo.Index
-import android.arch.persistence.room.vo.Pojo
-import android.arch.persistence.room.vo.PrimaryKey
-import android.arch.persistence.room.vo.Warning
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.AnnotationMirrors.getAnnotationValue
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import javax.lang.model.element.AnnotationMirror
-import javax.lang.model.element.AnnotationValue
-import javax.lang.model.element.Name
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-import javax.lang.model.util.SimpleAnnotationValueVisitor6
-
-class EntityProcessor(baseContext: Context,
-                      val element: TypeElement,
-                      private val referenceStack: LinkedHashSet<Name> = LinkedHashSet()) {
-    val context = baseContext.fork(element)
-
-    fun process(): Entity {
-        return context.cache.entities.get(Cache.EntityKey(element), {
-            doProcess()
-        })
-    }
-    private fun doProcess(): Entity {
-        context.checker.hasAnnotation(element, android.arch.persistence.room.Entity::class,
-                ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
-        val pojo = PojoProcessor(
-                baseContext = context,
-                element = element,
-                bindingScope = FieldProcessor.BindingScope.TWO_WAY,
-                parent = null,
-                referenceStack = referenceStack).process()
-        context.checker.check(pojo.relations.isEmpty(), element, RELATION_IN_ENTITY)
-        val annotation = MoreElements.getAnnotationMirror(element,
-                android.arch.persistence.room.Entity::class.java).orNull()
-        val tableName: String
-        val entityIndices: List<IndexInput>
-        val foreignKeyInputs: List<ForeignKeyInput>
-        val inheritSuperIndices: Boolean
-        if (annotation != null) {
-            tableName = extractTableName(element, annotation)
-            entityIndices = extractIndices(annotation, tableName)
-            inheritSuperIndices = AnnotationMirrors
-                    .getAnnotationValue(annotation, "inheritSuperIndices").getAsBoolean(false)
-            foreignKeyInputs = extractForeignKeys(annotation)
-        } else {
-            tableName = element.simpleName.toString()
-            foreignKeyInputs = emptyList()
-            entityIndices = emptyList()
-            inheritSuperIndices = false
-        }
-        context.checker.notBlank(tableName, element,
-                ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
-
-        val fieldIndices = pojo.fields
-                .filter { it.indexed }
-                .map {
-                    if (it.parent != null) {
-                        it.indexed = false
-                        context.logger.w(Warning.INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED, it.element,
-                                ProcessorErrors.droppedEmbeddedFieldIndex(
-                                        it.getPath(), element.qualifiedName.toString()))
-                        null
-                    } else if (it.element.enclosingElement != element && !inheritSuperIndices) {
-                        it.indexed = false
-                        context.logger.w(Warning.INDEX_FROM_PARENT_FIELD_IS_DROPPED,
-                                ProcessorErrors.droppedSuperClassFieldIndex(
-                                        it.columnName, element.toString(),
-                                        it.element.enclosingElement.toString()
-                                ))
-                        null
-                    } else {
-                        IndexInput(
-                                name = createIndexName(listOf(it.columnName), tableName),
-                                unique = false,
-                                columnNames = listOf(it.columnName)
-                        )
-                    }
-                }.filterNotNull()
-        val superIndices = loadSuperIndices(element.superclass, tableName, inheritSuperIndices)
-        val indexInputs = entityIndices + fieldIndices + superIndices
-        val indices = validateAndCreateIndices(indexInputs, pojo)
-
-        val primaryKey = findAndValidatePrimaryKey(pojo.fields, pojo.embeddedFields)
-        val affinity = primaryKey.fields.firstOrNull()?.affinity ?: SQLTypeAffinity.TEXT
-        context.checker.check(
-                !primaryKey.autoGenerateId || affinity == SQLTypeAffinity.INTEGER,
-                primaryKey.fields.firstOrNull()?.element ?: element,
-                ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT
-        )
-
-        val entityForeignKeys = validateAndCreateForeignKeyReferences(foreignKeyInputs, pojo)
-        checkIndicesForForeignKeys(entityForeignKeys, primaryKey, indices)
-
-        context.checker.check(SqlParser.isValidIdentifier(tableName), element,
-                ProcessorErrors.INVALID_TABLE_NAME)
-        pojo.fields.forEach {
-            context.checker.check(SqlParser.isValidIdentifier(it.columnName), it.element,
-                    ProcessorErrors.INVALID_COLUMN_NAME)
-        }
-
-        val entity = Entity(element = element,
-                tableName = tableName,
-                type = pojo.type,
-                fields = pojo.fields,
-                embeddedFields = pojo.embeddedFields,
-                indices = indices,
-                primaryKey = primaryKey,
-                foreignKeys = entityForeignKeys,
-                constructor = pojo.constructor)
-
-        return entity
-    }
-
-    private fun checkIndicesForForeignKeys(entityForeignKeys: List<ForeignKey>,
-                                           primaryKey: PrimaryKey,
-                                           indices: List<Index>) {
-        fun covers(columnNames: List<String>, fields: List<Field>): Boolean =
-            fields.size >= columnNames.size && columnNames.withIndex().all {
-                fields[it.index].columnName == it.value
-            }
-
-        entityForeignKeys.forEach { fKey ->
-            val columnNames = fKey.childFields.map { it.columnName }
-            val exists = covers(columnNames, primaryKey.fields) || indices.any { index ->
-                covers(columnNames, index.fields)
-            }
-            if (!exists) {
-                if (columnNames.size == 1) {
-                    context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element,
-                            ProcessorErrors.foreignKeyMissingIndexInChildColumn(columnNames[0]))
-                } else {
-                    context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element,
-                            ProcessorErrors.foreignKeyMissingIndexInChildColumns(columnNames))
-                }
-            }
-        }
-    }
-
-    /**
-     * Does a validation on foreign keys except the parent table's columns.
-     */
-    private fun validateAndCreateForeignKeyReferences(foreignKeyInputs: List<ForeignKeyInput>,
-                                                      pojo: Pojo): List<ForeignKey> {
-        return foreignKeyInputs.map {
-            if (it.onUpdate == null) {
-                context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
-                return@map null
-            }
-            if (it.onDelete == null) {
-                context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
-                return@map null
-            }
-            if (it.childColumns.isEmpty()) {
-                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST)
-                return@map null
-            }
-            if (it.parentColumns.isEmpty()) {
-                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST)
-                return@map null
-            }
-            if (it.childColumns.size != it.parentColumns.size) {
-                context.logger.e(element, ProcessorErrors.foreignKeyColumnNumberMismatch(
-                        it.childColumns, it.parentColumns
-                ))
-                return@map null
-            }
-            val parentElement = try {
-                MoreTypes.asElement(it.parent) as TypeElement
-            } catch (noClass: IllegalArgumentException) {
-                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_CANNOT_FIND_PARENT)
-                return@map null
-            }
-            val parentAnnotation = MoreElements.getAnnotationMirror(parentElement,
-                    android.arch.persistence.room.Entity::class.java).orNull()
-            if (parentAnnotation == null) {
-                context.logger.e(element,
-                        ProcessorErrors.foreignKeyNotAnEntity(parentElement.toString()))
-                return@map null
-            }
-            val tableName = extractTableName(parentElement, parentAnnotation)
-            val fields = it.childColumns.map { columnName ->
-                val field = pojo.fields.find { it.columnName == columnName }
-                if (field == null) {
-                    context.logger.e(pojo.element,
-                            ProcessorErrors.foreignKeyChildColumnDoesNotExist(columnName,
-                                    pojo.fields.map { it.columnName }))
-                }
-                field
-            }.filterNotNull()
-            if (fields.size != it.childColumns.size) {
-                return@map null
-            }
-            ForeignKey(
-                    parentTable = tableName,
-                    childFields = fields,
-                    parentColumns = it.parentColumns,
-                    onDelete = it.onDelete,
-                    onUpdate = it.onUpdate,
-                    deferred = it.deferred
-            )
-        }.filterNotNull()
-    }
-
-    private fun findAndValidatePrimaryKey(
-            fields: List<Field>, embeddedFields: List<EmbeddedField>): PrimaryKey {
-        val candidates = collectPrimaryKeysFromEntityAnnotations(element, fields) +
-                collectPrimaryKeysFromPrimaryKeyAnnotations(fields) +
-                collectPrimaryKeysFromEmbeddedFields(embeddedFields)
-
-        context.checker.check(candidates.isNotEmpty(), element, ProcessorErrors.MISSING_PRIMARY_KEY)
-
-        // 1. If a key is not autogenerated, but is Primary key or is part of Primary key we
-        // force the @NonNull annotation. If the key is a single Primary Key, Integer or Long, we
-        // don't force the @NonNull annotation since SQLite will automatically generate IDs.
-        // 2. If a key is autogenerate, we generate NOT NULL in table spec, but we don't require
-        // @NonNull annotation on the field itself.
-        candidates.filter { candidate -> !candidate.autoGenerateId }
-                .map { candidate ->
-                    candidate.fields.map { field ->
-                        if (candidate.fields.size > 1 ||
-                                (candidate.fields.size == 1
-                                        && field.affinity != SQLTypeAffinity.INTEGER)) {
-                            context.checker.check(field.nonNull, field.element,
-                                    ProcessorErrors.primaryKeyNull(field.getPath()))
-                            // Validate parents for nullability
-                            var parent = field.parent
-                            while (parent != null) {
-                                val parentField = parent.field
-                                context.checker.check(parentField.nonNull,
-                                        parentField.element,
-                                        ProcessorErrors.primaryKeyNull(parentField.getPath()))
-                                parent = parentField.parent
-                            }
-                        }
-                    }
-                }
-
-        if (candidates.size == 1) {
-            // easy :)
-            return candidates.first()
-        }
-
-        return choosePrimaryKey(candidates, element)
-    }
-
-    /**
-     * Check fields for @PrimaryKey.
-     */
-    private fun collectPrimaryKeysFromPrimaryKeyAnnotations(fields: List<Field>): List<PrimaryKey> {
-        return fields.map { field ->
-            MoreElements.getAnnotationMirror(field.element,
-                    android.arch.persistence.room.PrimaryKey::class.java).orNull()?.let {
-                if (field.parent != null) {
-                    // the field in the entity that contains this error.
-                    val grandParentField = field.parent.mRootParent.field.element
-                    // bound for entity.
-                    context.fork(grandParentField).logger.w(
-                            Warning.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED,
-                            grandParentField,
-                            ProcessorErrors.embeddedPrimaryKeyIsDropped(
-                                    element.qualifiedName.toString(), field.name))
-                    null
-                } else {
-                    PrimaryKey(declaredIn = field.element.enclosingElement,
-                            fields = listOf(field),
-                            autoGenerateId = AnnotationMirrors
-                                    .getAnnotationValue(it, "autoGenerate")
-                                    .getAsBoolean(false))
-                }
-            }
-        }.filterNotNull()
-    }
-
-    /**
-     * Check classes for @Entity(primaryKeys = ?).
-     */
-    private fun collectPrimaryKeysFromEntityAnnotations(
-            typeElement: TypeElement, availableFields: List<Field>): List<PrimaryKey> {
-        val myPkeys = MoreElements.getAnnotationMirror(typeElement,
-                android.arch.persistence.room.Entity::class.java).orNull()?.let {
-            val primaryKeyColumns = AnnotationMirrors.getAnnotationValue(it, "primaryKeys")
-                    .getAsStringList()
-            if (primaryKeyColumns.isEmpty()) {
-                emptyList<PrimaryKey>()
-            } else {
-                val fields = primaryKeyColumns.map { pKeyColumnName ->
-                    val field = availableFields.firstOrNull { it.columnName == pKeyColumnName }
-                    context.checker.check(field != null, typeElement,
-                            ProcessorErrors.primaryKeyColumnDoesNotExist(pKeyColumnName,
-                                    availableFields.map { it.columnName }))
-                    field
-                }.filterNotNull()
-                listOf(PrimaryKey(declaredIn = typeElement,
-                        fields = fields,
-                        autoGenerateId = false))
-            }
-        } ?: emptyList<PrimaryKey>()
-        // checks supers.
-        val mySuper = typeElement.superclass
-        val superPKeys = if (mySuper != null && mySuper.kind != TypeKind.NONE) {
-            // my super cannot see my fields so remove them.
-            val remainingFields = availableFields.filterNot {
-                it.element.enclosingElement == typeElement
-            }
-            collectPrimaryKeysFromEntityAnnotations(
-                    MoreTypes.asTypeElement(mySuper), remainingFields)
-        } else {
-            emptyList()
-        }
-        return superPKeys + myPkeys
-    }
-
-    private fun collectPrimaryKeysFromEmbeddedFields(
-            embeddedFields: List<EmbeddedField>): List<PrimaryKey> {
-        return embeddedFields.map { embeddedField ->
-            MoreElements.getAnnotationMirror(embeddedField.field.element,
-                    android.arch.persistence.room.PrimaryKey::class.java).orNull()?.let {
-                val autoGenerate = AnnotationMirrors
-                        .getAnnotationValue(it, "autoGenerate").getAsBoolean(false)
-                context.checker.check(!autoGenerate || embeddedField.pojo.fields.size == 1,
-                        embeddedField.field.element,
-                        ProcessorErrors.AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS)
-                PrimaryKey(declaredIn = embeddedField.field.element.enclosingElement,
-                        fields = embeddedField.pojo.fields,
-                        autoGenerateId = autoGenerate)
-            }
-        }.filterNotNull()
-    }
-
-    // start from my element and check if anywhere in the list we can find the only well defined
-    // pkey, if so, use it.
-    private fun choosePrimaryKey(
-            candidates: List<PrimaryKey>, typeElement: TypeElement): PrimaryKey {
-        // If 1 of these primary keys is declared in this class, then it is the winner. Just print
-        //    a note for the others.
-        // If 0 is declared, check the parent.
-        // If more than 1 primary key is declared in this class, it is an error.
-        val myPKeys = candidates.filter { candidate ->
-            candidate.declaredIn == typeElement
-        }
-        return if (myPKeys.size == 1) {
-            // just note, this is not worth an error or warning
-            (candidates - myPKeys).forEach {
-                context.logger.d(element,
-                        "${it.toHumanReadableString()} is" +
-                                " overridden by ${myPKeys.first().toHumanReadableString()}")
-            }
-            myPKeys.first()
-        } else if (myPKeys.isEmpty()) {
-            // i have not declared anything, delegate to super
-            val mySuper = typeElement.superclass
-            if (mySuper != null && mySuper.kind != TypeKind.NONE) {
-                return choosePrimaryKey(candidates, MoreTypes.asTypeElement(mySuper))
-            }
-            PrimaryKey.MISSING
-        } else {
-            context.logger.e(element, ProcessorErrors.multiplePrimaryKeyAnnotations(
-                    myPKeys.map(PrimaryKey::toHumanReadableString)))
-            PrimaryKey.MISSING
-        }
-    }
-
-    private fun validateAndCreateIndices(
-            inputs: List<IndexInput>, pojo: Pojo): List<Index> {
-        // check for columns
-        val indices = inputs.map { input ->
-            context.checker.check(input.columnNames.isNotEmpty(), element,
-                    INDEX_COLUMNS_CANNOT_BE_EMPTY)
-            val fields = input.columnNames.map { columnName ->
-                val field = pojo.fields.firstOrNull {
-                    it.columnName == columnName
-                }
-                context.checker.check(field != null, element,
-                        ProcessorErrors.indexColumnDoesNotExist(
-                                columnName, pojo.fields.map { it.columnName }
-                        ))
-                field
-            }.filterNotNull()
-            if (fields.isEmpty()) {
-                null
-            } else {
-                Index(name = input.name, unique = input.unique, fields = fields)
-            }
-        }.filterNotNull()
-
-        // check for duplicate indices
-        indices
-                .groupBy { it.name }
-                .filter { it.value.size > 1 }
-                .forEach {
-                    context.logger.e(element, ProcessorErrors.duplicateIndexInEntity(it.key))
-                }
-
-        // see if any embedded field is an entity with indices, if so, report a warning
-        pojo.embeddedFields.forEach { embedded ->
-            val embeddedElement = embedded.pojo.element
-            val subEntityAnnotation = MoreElements.getAnnotationMirror(embeddedElement,
-                    android.arch.persistence.room.Entity::class.java).orNull()
-            subEntityAnnotation?.let {
-                val subIndices = extractIndices(subEntityAnnotation, "")
-                if (subIndices.isNotEmpty()) {
-                    context.logger.w(Warning.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED,
-                            embedded.field.element, ProcessorErrors.droppedEmbeddedIndex(
-                            entityName = embedded.pojo.typeName.toString(),
-                            fieldPath = embedded.field.getPath(),
-                            grandParent = element.qualifiedName.toString()))
-                }
-            }
-        }
-        return indices
-    }
-
-    // check if parent is an Entity, if so, report its annotation indices
-    private fun loadSuperIndices(
-            typeMirror: TypeMirror?, tableName: String, inherit: Boolean): List<IndexInput> {
-        if (typeMirror == null || typeMirror.kind == TypeKind.NONE) {
-            return emptyList()
-        }
-        val parentElement = MoreTypes.asTypeElement(typeMirror)
-        val myIndices = MoreElements.getAnnotationMirror(parentElement,
-                android.arch.persistence.room.Entity::class.java).orNull()?.let { annotation ->
-            val indices = extractIndices(annotation, tableName = "super")
-            if (indices.isEmpty()) {
-                emptyList()
-            } else if (inherit) {
-                // rename them
-                indices.map {
-                    IndexInput(
-                            name = createIndexName(it.columnNames, tableName),
-                            unique = it.unique,
-                            columnNames = it.columnNames)
-                }
-            } else {
-                context.logger.w(Warning.INDEX_FROM_PARENT_IS_DROPPED,
-                        parentElement,
-                        ProcessorErrors.droppedSuperClassIndex(
-                                childEntity = element.qualifiedName.toString(),
-                                superEntity = parentElement.qualifiedName.toString()))
-                emptyList()
-            }
-        } ?: emptyList()
-        return myIndices + loadSuperIndices(parentElement.superclass, tableName, inherit)
-    }
-
-    companion object {
-        fun extractTableName(element: TypeElement, annotation: AnnotationMirror): String {
-            val annotationValue = AnnotationMirrors
-                    .getAnnotationValue(annotation, "tableName").value.toString()
-            return if (annotationValue == "") {
-                element.simpleName.toString()
-            } else {
-                annotationValue
-            }
-        }
-
-        private fun extractIndices(
-                annotation: AnnotationMirror, tableName: String): List<IndexInput> {
-            val arrayOfIndexAnnotations = AnnotationMirrors.getAnnotationValue(annotation,
-                    "indices")
-            return INDEX_LIST_VISITOR.visit(arrayOfIndexAnnotations, tableName)
-        }
-
-        private val INDEX_LIST_VISITOR = object
-            : SimpleAnnotationValueVisitor6<List<IndexInput>, String>() {
-            override fun visitArray(
-                    values: MutableList<out AnnotationValue>?,
-                    tableName: String
-            ): List<IndexInput> {
-                return values?.map {
-                    INDEX_VISITOR.visit(it, tableName)
-                }?.filterNotNull() ?: emptyList<IndexInput>()
-            }
-        }
-
-        private val INDEX_VISITOR = object : SimpleAnnotationValueVisitor6<IndexInput?, String>() {
-            override fun visitAnnotation(a: AnnotationMirror?, tableName: String): IndexInput? {
-                val fieldInput = getAnnotationValue(a, "value").getAsStringList()
-                val unique = getAnnotationValue(a, "unique").getAsBoolean(false)
-                val nameValue = getAnnotationValue(a, "name")
-                        .getAsString("")
-                val name = if (nameValue == null || nameValue == "") {
-                    createIndexName(fieldInput, tableName)
-                } else {
-                    nameValue
-                }
-                return IndexInput(name, unique, fieldInput)
-            }
-        }
-
-        private fun createIndexName(columnNames: List<String>, tableName: String): String {
-            return Index.DEFAULT_PREFIX + tableName + "_" + columnNames.joinToString("_")
-        }
-
-        private fun extractForeignKeys(annotation: AnnotationMirror): List<ForeignKeyInput> {
-            val arrayOfForeignKeyAnnotations = getAnnotationValue(annotation, "foreignKeys")
-            return FOREIGN_KEY_LIST_VISITOR.visit(arrayOfForeignKeyAnnotations)
-        }
-
-        private val FOREIGN_KEY_LIST_VISITOR = object
-            : SimpleAnnotationValueVisitor6<List<ForeignKeyInput>, Void?>() {
-            override fun visitArray(
-                    values: MutableList<out AnnotationValue>?,
-                    void: Void?
-            ): List<ForeignKeyInput> {
-                return values?.map {
-                    FOREIGN_KEY_VISITOR.visit(it)
-                }?.filterNotNull() ?: emptyList<ForeignKeyInput>()
-            }
-        }
-
-        private val FOREIGN_KEY_VISITOR = object : SimpleAnnotationValueVisitor6<ForeignKeyInput?,
-                Void?>() {
-            override fun visitAnnotation(a: AnnotationMirror?, void: Void?): ForeignKeyInput? {
-                val entityClass = try {
-                    getAnnotationValue(a, "entity").toType()
-                } catch (notPresent: TypeNotPresentException) {
-                    return null
-                }
-                val parentColumns = getAnnotationValue(a, "parentColumns").getAsStringList()
-                val childColumns = getAnnotationValue(a, "childColumns").getAsStringList()
-                val onDeleteInput = getAnnotationValue(a, "onDelete").getAsInt()
-                val onUpdateInput = getAnnotationValue(a, "onUpdate").getAsInt()
-                val deferred = getAnnotationValue(a, "deferred").getAsBoolean(true)
-                val onDelete = ForeignKeyAction.fromAnnotationValue(onDeleteInput)
-                val onUpdate = ForeignKeyAction.fromAnnotationValue(onUpdateInput)
-                return ForeignKeyInput(
-                        parent = entityClass,
-                        parentColumns = parentColumns,
-                        childColumns = childColumns,
-                        onDelete = onDelete,
-                        onUpdate = onUpdate,
-                        deferred = deferred)
-            }
-        }
-    }
-
-    /**
-     * processed Index annotation output
-     */
-    data class IndexInput(val name: String, val unique: Boolean, val columnNames: List<String>)
-
-    /**
-     * ForeignKey, before it is processed in the context of a database.
-     */
-    data class ForeignKeyInput(
-            val parent: TypeMirror,
-            val parentColumns: List<String>,
-            val childColumns: List<String>,
-            val onDelete: ForeignKeyAction?,
-            val onUpdate: ForeignKeyAction?,
-            val deferred: Boolean)
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/FieldProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/FieldProcessor.kt
deleted file mode 100644
index f03d7da..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/FieldProcessor.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.ColumnInfo
-import android.arch.persistence.room.ext.getAsBoolean
-import android.arch.persistence.room.ext.getAsInt
-import android.arch.persistence.room.ext.getAsString
-import android.arch.persistence.room.parser.Collate
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.vo.EmbeddedField
-import android.arch.persistence.room.vo.Field
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.Element
-import javax.lang.model.type.DeclaredType
-
-class FieldProcessor(baseContext: Context, val containing: DeclaredType, val element: Element,
-                     val bindingScope: BindingScope,
-                     // pass only if this is processed as a child of Embedded field
-                     val fieldParent: EmbeddedField?) {
-    val context = baseContext.fork(element)
-    fun process(): Field {
-        val member = context.processingEnv.typeUtils.asMemberOf(containing, element)
-        val type = TypeName.get(member)
-        val columnInfoAnnotation = MoreElements.getAnnotationMirror(element,
-                ColumnInfo::class.java)
-        val name = element.simpleName.toString()
-        val columnName: String
-        val affinity: SQLTypeAffinity?
-        val collate: Collate?
-        val fieldPrefix = fieldParent?.prefix ?: ""
-        val indexed: Boolean
-        if (columnInfoAnnotation.isPresent) {
-            val nameInAnnotation = AnnotationMirrors
-                    .getAnnotationValue(columnInfoAnnotation.get(), "name")
-                    .getAsString(ColumnInfo.INHERIT_FIELD_NAME)
-            columnName = fieldPrefix + if (nameInAnnotation == ColumnInfo.INHERIT_FIELD_NAME) {
-                name
-            } else {
-                nameInAnnotation
-            }
-
-            affinity = try {
-                val userDefinedAffinity = AnnotationMirrors
-                        .getAnnotationValue(columnInfoAnnotation.get(), "typeAffinity")
-                        .getAsInt(ColumnInfo.UNDEFINED)!!
-                SQLTypeAffinity.fromAnnotationValue(userDefinedAffinity)
-            } catch (ex: NumberFormatException) {
-                null
-            }
-
-            collate = Collate.fromAnnotationValue(AnnotationMirrors.getAnnotationValue(
-                    columnInfoAnnotation.get(), "collate").getAsInt(ColumnInfo.UNSPECIFIED)!!)
-
-            indexed = AnnotationMirrors
-                    .getAnnotationValue(columnInfoAnnotation.get(), "index")
-                    .getAsBoolean(false)
-        } else {
-            columnName = fieldPrefix + name
-            affinity = null
-            collate = null
-            indexed = false
-        }
-        context.checker.notBlank(columnName, element,
-                ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
-        context.checker.notUnbound(type, element,
-                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
-
-        val field = Field(name = name,
-                type = member,
-                element = element,
-                columnName = columnName,
-                affinity = affinity,
-                collate = collate,
-                parent = fieldParent,
-                indexed = indexed)
-
-        when (bindingScope) {
-            BindingScope.TWO_WAY -> {
-                val adapter = context.typeAdapterStore.findColumnTypeAdapter(field.type,
-                        field.affinity)
-                field.statementBinder = adapter
-                field.cursorValueReader = adapter
-                field.affinity = adapter?.typeAffinity ?: field.affinity
-                context.checker.check(adapter != null, field.element,
-                        ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
-            }
-            BindingScope.BIND_TO_STMT -> {
-                field.statementBinder = context.typeAdapterStore
-                        .findStatementValueBinder(field.type, field.affinity)
-                context.checker.check(field.statementBinder != null, field.element,
-                        ProcessorErrors.CANNOT_FIND_STMT_BINDER)
-            }
-            BindingScope.READ_FROM_CURSOR -> {
-                field.cursorValueReader = context.typeAdapterStore
-                        .findCursorValueReader(field.type, field.affinity)
-                context.checker.check(field.cursorValueReader != null, field.element,
-                        ProcessorErrors.CANNOT_FIND_CURSOR_READER)
-            }
-        }
-        return field
-    }
-
-    /**
-     * Defines what we need to assign
-     */
-    enum class BindingScope {
-        TWO_WAY, // both bind and read.
-        BIND_TO_STMT, // just value to statement
-        READ_FROM_CURSOR // just cursor to value
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessor.kt
deleted file mode 100644
index 02b191c..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessor.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-
-package android.arch.persistence.room.processor
-
-import android.support.annotation.VisibleForTesting
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.OnConflictStrategy.IGNORE
-import android.arch.persistence.room.OnConflictStrategy.REPLACE
-import android.arch.persistence.room.vo.InsertionMethod
-import android.arch.persistence.room.vo.InsertionMethod.Type
-import android.arch.persistence.room.vo.ShortcutQueryParameter
-import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeKind.LONG
-import javax.lang.model.type.TypeKind.VOID
-import javax.lang.model.type.TypeMirror
-
-class InsertionMethodProcessor(baseContext: Context,
-                               val containing: DeclaredType,
-                               val executableElement: ExecutableElement) {
-    val context = baseContext.fork(executableElement)
-    fun process(): InsertionMethod {
-        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
-        val annotation = delegate.extractAnnotation(Insert::class,
-                ProcessorErrors.MISSING_INSERT_ANNOTATION)
-
-        val onConflict = OnConflictProcessor.extractFrom(annotation)
-        context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE,
-                executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
-
-        val returnType = delegate.extractReturnType()
-        val returnTypeName = TypeName.get(returnType)
-        context.checker.notUnbound(returnTypeName, executableElement,
-                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS)
-
-        val (entities, params) = delegate.extractParams(
-                missingParamError = ProcessorErrors
-                        .INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
-        )
-
-        // TODO we can support more types
-        var insertionType = getInsertionType(returnType)
-        context.checker.check(insertionType != null, executableElement,
-                ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE)
-
-        if (insertionType != null) {
-            val acceptable = acceptableTypes(params)
-            if (insertionType !in acceptable) {
-                context.logger.e(executableElement,
-                        ProcessorErrors.insertionMethodReturnTypeMismatch(
-                                insertionType.returnTypeName,
-                                acceptable.map { it.returnTypeName }))
-                // clear it, no reason to generate code for it.
-                insertionType = null
-            }
-        }
-        return InsertionMethod(
-                element = executableElement,
-                name = executableElement.simpleName.toString(),
-                returnType = returnType,
-                entities = entities,
-                parameters = params,
-                onConflict = onConflict,
-                insertionType = insertionType
-        )
-    }
-
-    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-    private fun getInsertionType(returnType: TypeMirror): InsertionMethod.Type? {
-        // TODO we need to support more types here.
-        fun isLongPrimitiveType(typeMirror: TypeMirror) = typeMirror.kind == LONG
-
-        fun isLongBoxType(typeMirror: TypeMirror) =
-                MoreTypes.isType(typeMirror) &&
-                        MoreTypes.isTypeOf(java.lang.Long::class.java, typeMirror)
-
-        fun isLongType(typeMirror: TypeMirror) =
-                isLongPrimitiveType(typeMirror) || isLongBoxType(typeMirror)
-
-        return if (returnType.kind == VOID) {
-            Type.INSERT_VOID
-        } else if (returnType.kind == TypeKind.ARRAY) {
-            val arrayType = MoreTypes.asArray(returnType)
-            val param = arrayType.componentType
-            if (isLongPrimitiveType(param)) {
-                Type.INSERT_ID_ARRAY
-            } else if (isLongBoxType(param)) {
-                Type.INSERT_ID_ARRAY_BOX
-            } else {
-                null
-            }
-        } else if (MoreTypes.isType(returnType)
-                && MoreTypes.isTypeOf(List::class.java, returnType)) {
-            val declared = MoreTypes.asDeclared(returnType)
-            val param = declared.typeArguments.first()
-            if (isLongBoxType(param)) {
-                Type.INSERT_ID_LIST
-            } else {
-                null
-            }
-        } else if (isLongType(returnType)) {
-            Type.INSERT_SINGLE_ID
-        } else {
-            null
-        }
-    }
-
-    companion object {
-        @VisibleForTesting
-        val VOID_SET by lazy { setOf(Type.INSERT_VOID) }
-        @VisibleForTesting
-        val SINGLE_ITEM_SET by lazy { setOf(Type.INSERT_VOID, Type.INSERT_SINGLE_ID) }
-        @VisibleForTesting
-        val MULTIPLE_ITEM_SET by lazy {
-            setOf(Type.INSERT_VOID, Type.INSERT_ID_ARRAY, Type.INSERT_ID_ARRAY_BOX,
-                    Type.INSERT_ID_LIST)
-        }
-        fun acceptableTypes(params: List<ShortcutQueryParameter>): Set<InsertionMethod.Type> {
-            if (params.isEmpty()) {
-                return VOID_SET
-            }
-            if (params.size > 1) {
-                return VOID_SET
-            }
-            if (params.first().isMultiple) {
-                return MULTIPLE_ITEM_SET
-            } else {
-                return SINGLE_ITEM_SET
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/OnConflictProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/OnConflictProcessor.kt
deleted file mode 100644
index ac5ed5b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/OnConflictProcessor.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.OnConflictStrategy
-import com.google.auto.common.AnnotationMirrors
-import javax.lang.model.element.AnnotationMirror
-
-/**
- * Processes on conflict fields in annotations
- */
-object OnConflictProcessor {
-    val INVALID_ON_CONFLICT = -1
-
-    @OnConflictStrategy
-    fun extractFrom(annotation: AnnotationMirror?, fieldName: String = "onConflict"): Int {
-        return if (annotation == null) {
-            INVALID_ON_CONFLICT
-        } else {
-            try {
-                val onConflictValue = AnnotationMirrors
-                        .getAnnotationValue(annotation, fieldName)
-                        .value
-                onConflictValue.toString().toInt()
-            } catch (ex: NumberFormatException) {
-                INVALID_ON_CONFLICT
-            }
-        }
-    }
-
-    fun onConflictText(@OnConflictStrategy onConflict: Int): String {
-        return when (onConflict) {
-            OnConflictStrategy.REPLACE -> "REPLACE"
-            OnConflictStrategy.ABORT -> "ABORT"
-            OnConflictStrategy.FAIL -> "FAIL"
-            OnConflictStrategy.IGNORE -> "IGNORE"
-            OnConflictStrategy.ROLLBACK -> "ROLLBACK"
-            else -> "BAD_CONFLICT_CONSTRAINT"
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoMethodProcessor.kt
deleted file mode 100644
index e08b816..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoMethodProcessor.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2018 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.processor
-
-import android.arch.persistence.room.vo.PojoMethod
-import com.google.auto.common.MoreTypes
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-
-/**
- * processes an executable element as member of the owning class
- */
-class PojoMethodProcessor(
-        private val context: Context,
-        private val element: ExecutableElement,
-        private val owner: DeclaredType) {
-    fun process(): PojoMethod {
-        val asMember = context.processingEnv.typeUtils.asMemberOf(owner, element)
-        val name = element.simpleName.toString()
-        return PojoMethod(
-                element = element,
-                resolvedType = MoreTypes.asExecutable(asMember),
-                name = name
-        )
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt
deleted file mode 100644
index 6ed4034..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/PojoProcessor.kt
+++ /dev/null
@@ -1,726 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.ColumnInfo
-import android.arch.persistence.room.Embedded
-import android.arch.persistence.room.Ignore
-import android.arch.persistence.room.Relation
-import android.arch.persistence.room.ext.KotlinMetadataProcessor
-import android.arch.persistence.room.ext.getAllFieldsIncludingPrivateSupers
-import android.arch.persistence.room.ext.getAnnotationValue
-import android.arch.persistence.room.ext.getAsString
-import android.arch.persistence.room.ext.getAsStringList
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.hasAnyOf
-import android.arch.persistence.room.ext.isAssignableWithoutVariance
-import android.arch.persistence.room.ext.isCollection
-import android.arch.persistence.room.ext.toClassType
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
-import android.arch.persistence.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
-import android.arch.persistence.room.processor.cache.Cache
-import android.arch.persistence.room.vo.CallType
-import android.arch.persistence.room.vo.Constructor
-import android.arch.persistence.room.vo.EmbeddedField
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.FieldGetter
-import android.arch.persistence.room.vo.FieldSetter
-import android.arch.persistence.room.vo.Pojo
-import android.arch.persistence.room.vo.PojoMethod
-import android.arch.persistence.room.vo.Warning
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
-import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier.ABSTRACT
-import javax.lang.model.element.Modifier.PRIVATE
-import javax.lang.model.element.Modifier.PROTECTED
-import javax.lang.model.element.Modifier.PUBLIC
-import javax.lang.model.element.Modifier.STATIC
-import javax.lang.model.element.Modifier.TRANSIENT
-import javax.lang.model.element.Name
-import javax.lang.model.element.TypeElement
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-import javax.lang.model.util.ElementFilter
-
-/**
- * Processes any class as if it is a Pojo.
- */
-class PojoProcessor(
-        baseContext: Context,
-        val element: TypeElement,
-        val bindingScope: FieldProcessor.BindingScope,
-        val parent: EmbeddedField?,
-        val referenceStack: LinkedHashSet<Name> = LinkedHashSet())
-    : KotlinMetadataProcessor {
-    val context = baseContext.fork(element)
-
-    // for KotlinMetadataUtils
-    override val processingEnv: ProcessingEnvironment
-        get() = context.processingEnv
-
-    // opportunistic kotlin metadata
-    private val kotlinMetadata by lazy {
-        try {
-            element.kotlinMetadata
-        } catch (throwable: Throwable) {
-            context.logger.d(element, "failed to read get kotlin metadata from %s", element)
-        } as? KotlinClassMetadata
-    }
-
-    companion object {
-        val PROCESSED_ANNOTATIONS = listOf(ColumnInfo::class, Embedded::class,
-                Relation::class)
-    }
-
-    fun process(): Pojo {
-        return context.cache.pojos.get(Cache.PojoKey(element, bindingScope, parent), {
-            referenceStack.add(element.qualifiedName)
-            try {
-                doProcess()
-            } finally {
-                referenceStack.remove(element.qualifiedName)
-            }
-        })
-    }
-
-    private fun doProcess(): Pojo {
-        val declaredType = MoreTypes.asDeclared(element.asType())
-        // TODO handle conflicts with super: b/35568142
-        val allFields = element.getAllFieldsIncludingPrivateSupers(context.processingEnv)
-                .filter {
-                    !it.hasAnnotation(Ignore::class)
-                            && !it.hasAnyOf(STATIC)
-                            && (!it.hasAnyOf(TRANSIENT)
-                            || it.hasAnnotation(ColumnInfo::class)
-                            || it.hasAnnotation(Embedded::class)
-                            || it.hasAnnotation(Relation::class))
-                }
-                .groupBy { field ->
-                    context.checker.check(
-                            PROCESSED_ANNOTATIONS.count { field.hasAnnotation(it) } < 2, field,
-                            ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION
-                    )
-                    if (field.hasAnnotation(Embedded::class)) {
-                        Embedded::class
-                    } else if (field.hasAnnotation(Relation::class)) {
-                        Relation::class
-                    } else {
-                        null
-                    }
-                }
-
-        val myFields = allFields[null]
-                ?.map {
-                    FieldProcessor(
-                            baseContext = context,
-                            containing = declaredType,
-                            element = it,
-                            bindingScope = bindingScope,
-                            fieldParent = parent).process()
-                } ?: emptyList()
-
-        val embeddedFields =
-                allFields[Embedded::class]
-                        ?.map {
-                            processEmbeddedField(declaredType, it)
-                        }
-                        ?.filterNotNull()
-                        ?: emptyList()
-
-        val subFields = embeddedFields.flatMap { it.pojo.fields }
-        val fields = myFields + subFields
-
-        val myRelationsList = allFields[Relation::class]
-                ?.map {
-                    processRelationField(fields, declaredType, it)
-                }
-                ?.filterNotNull()
-                ?: emptyList()
-
-        val subRelations = embeddedFields.flatMap { it.pojo.relations }
-        val relations = myRelationsList + subRelations
-
-        fields.groupBy { it.columnName }
-                .filter { it.value.size > 1 }
-                .forEach {
-                    context.logger.e(element, ProcessorErrors.pojoDuplicateFieldNames(
-                            it.key, it.value.map(Field::getPath)
-                    ))
-                    it.value.forEach {
-                        context.logger.e(it.element, POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME)
-                    }
-                }
-
-        val methods = MoreElements.getLocalAndInheritedMethods(element,
-                context.processingEnv.elementUtils)
-                .filter {
-                    !it.hasAnyOf(PRIVATE, ABSTRACT, STATIC)
-                            && !it.hasAnnotation(Ignore::class)
-                }
-                .map { MoreElements.asExecutable(it) }
-                .map {
-                    PojoMethodProcessor(
-                            context = context,
-                            element = it,
-                            owner = declaredType
-                    ).process()
-                }
-
-        val getterCandidates = methods.filter {
-            it.element.parameters.size == 0 && it.resolvedType.returnType.kind != TypeKind.VOID
-        }
-
-        val setterCandidates = methods.filter {
-            it.element.parameters.size == 1 && it.resolvedType.returnType.kind == TypeKind.VOID
-        }
-
-        // don't try to find a constructor for binding to statement.
-        val constructor = if (bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT) {
-            // we don't need to construct this POJO.
-            null
-        } else {
-            chooseConstructor(myFields, embeddedFields, relations)
-        }
-
-        assignGetters(myFields, getterCandidates)
-        assignSetters(myFields, setterCandidates, constructor)
-
-        embeddedFields.forEach {
-            assignGetter(it.field, getterCandidates)
-            assignSetter(it.field, setterCandidates, constructor)
-        }
-
-        myRelationsList.forEach {
-            assignGetter(it.field, getterCandidates)
-            assignSetter(it.field, setterCandidates, constructor)
-        }
-
-        return Pojo(element = element,
-                type = declaredType,
-                fields = fields,
-                embeddedFields = embeddedFields,
-                relations = relations,
-                constructor = constructor)
-    }
-
-    /**
-     * Retrieves the parameter names of a method. If the method is inherited from a dependency
-     * module, the parameter name is not available (not in java spec). For kotlin, since parameter
-     * names are part of the API, we can read them via the kotlin metadata annotation.
-     * <p>
-     * Since we are using an unofficial library to read the metadata, all access to that code
-     * is safe guarded to avoid unexpected failures. In other words, it is a best effort but
-     * better than not supporting these until JB provides a proper API.
-     */
-    private fun getParamNames(method: ExecutableElement): List<String> {
-        val paramNames = method.parameters.map { it.simpleName.toString() }
-        if (paramNames.isEmpty()) {
-            return emptyList()
-        }
-        return kotlinMetadata?.getParameterNames(method) ?: paramNames
-    }
-
-    private fun chooseConstructor(
-            myFields: List<Field>,
-            embedded: List<EmbeddedField>,
-            relations: List<android.arch.persistence.room.vo.Relation>): Constructor? {
-        val constructors = ElementFilter.constructorsIn(element.enclosedElements)
-                .filterNot { it.hasAnnotation(Ignore::class) || it.hasAnyOf(PRIVATE) }
-        val fieldMap = myFields.associateBy { it.name }
-        val embeddedMap = embedded.associateBy { it.field.name }
-        val typeUtils = context.processingEnv.typeUtils
-        // list of param names -> matched params pairs for each failed constructor
-        val failedConstructors = arrayListOf<FailedConstructor>()
-        // if developer puts a relation into a constructor, it is usually an error but if there
-        // is another constructor that is good, we can ignore the error. b/72884434
-        val relationsInConstructor = arrayListOf<VariableElement>()
-        val goodConstructors = constructors.map { constructor ->
-            val parameterNames = getParamNames(constructor)
-            val params = constructor.parameters.mapIndexed param@ { index, param ->
-                val paramName = parameterNames[index]
-                val paramType = param.asType()
-
-                val matches = fun(field: Field?): Boolean {
-                    return if (field == null) {
-                        false
-                    } else if (!field.nameWithVariations.contains(paramName)) {
-                        false
-                    } else {
-                        // see: b/69164099
-                        typeUtils.isAssignableWithoutVariance(paramType, field.type)
-                    }
-                }
-
-                val exactFieldMatch = fieldMap[paramName]
-
-                if (matches(exactFieldMatch)) {
-                    return@param Constructor.FieldParam(exactFieldMatch!!)
-                }
-                val exactEmbeddedMatch = embeddedMap[paramName]
-                if (matches(exactEmbeddedMatch?.field)) {
-                    return@param Constructor.EmbeddedParam(exactEmbeddedMatch!!)
-                }
-
-                val matchingFields = myFields.filter {
-                    matches(it)
-                }
-                val embeddedMatches = embedded.filter {
-                    matches(it.field)
-                }
-                if (matchingFields.isEmpty() && embeddedMatches.isEmpty()) {
-                    // if it didn't match a proper field, a common mistake is to have a relation
-                    // so check to see if it is a relation
-                    val matchedRelation = relations.any {
-                        it.field.nameWithVariations.contains(paramName)
-                    }
-                    if (matchedRelation) {
-                        relationsInConstructor.add(param)
-                    }
-                    null
-                } else if (matchingFields.size + embeddedMatches.size == 1) {
-                    if (matchingFields.isNotEmpty()) {
-                        Constructor.FieldParam(matchingFields.first())
-                    } else {
-                        Constructor.EmbeddedParam(embeddedMatches.first())
-                    }
-                } else {
-                    context.logger.e(param, ProcessorErrors.ambigiousConstructor(
-                            pojo = element.qualifiedName.toString(),
-                            paramName = paramName,
-                            matchingFields = matchingFields.map { it.getPath() }
-                                    + embedded.map { it.field.getPath() }
-                    ))
-                    null
-                }
-            }
-            if (params.any { it == null }) {
-                failedConstructors.add(FailedConstructor(constructor, parameterNames, params))
-                null
-            } else {
-                @Suppress("UNCHECKED_CAST")
-                Constructor(constructor, params as List<Constructor.Param>)
-            }
-        }.filterNotNull()
-        when {
-            goodConstructors.isEmpty() -> {
-                relationsInConstructor.forEach {
-                    context.logger.e(it,
-                            ProcessorErrors.RELATION_CANNOT_BE_CONSTRUCTOR_PARAMETER)
-                }
-                if (failedConstructors.isNotEmpty()) {
-                    val failureMsg = failedConstructors.joinToString("\n") { entry ->
-                        entry.log()
-                    }
-                    context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR +
-                            "\nTried the following constructors but they failed to match:" +
-                            "\n$failureMsg")
-                }
-                context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
-                return null
-            }
-            goodConstructors.size > 1 -> {
-                // if there is a no-arg constructor, pick it. Even though it is weird, easily happens
-                // with kotlin data classes.
-                val noArg = goodConstructors.firstOrNull { it.params.isEmpty() }
-                if (noArg != null) {
-                    context.logger.w(Warning.DEFAULT_CONSTRUCTOR, element,
-                            ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS_CHOOSING_NO_ARG)
-                    return noArg
-                }
-                goodConstructors.forEach {
-                    context.logger.e(it.element, ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
-                }
-                return null
-            }
-            else -> return goodConstructors.first()
-        }
-    }
-
-    private fun processEmbeddedField(
-            declaredType: DeclaredType?, variableElement: VariableElement): EmbeddedField? {
-
-        val asTypeElement = MoreTypes.asTypeElement(variableElement.asType())
-
-        if (detectReferenceRecursion(asTypeElement)) {
-            return null
-        }
-
-        val fieldPrefix = variableElement
-                .getAnnotationValue(Embedded::class.java, "prefix")
-                ?.toString()
-                ?: ""
-        val inheritedPrefix = parent?.prefix ?: ""
-        val embeddedField = Field(
-                variableElement,
-                variableElement.simpleName.toString(),
-                type = context
-                        .processingEnv
-                        .typeUtils
-                        .asMemberOf(declaredType, variableElement),
-                affinity = null,
-                parent = parent)
-        val subParent = EmbeddedField(
-                field = embeddedField,
-                prefix = inheritedPrefix + fieldPrefix,
-                parent = parent)
-        subParent.pojo = PojoProcessor(
-                baseContext = context.fork(variableElement),
-                element = asTypeElement,
-                bindingScope = bindingScope,
-                parent = subParent,
-                referenceStack = referenceStack).process()
-        return subParent
-    }
-
-    private fun processRelationField(
-            myFields: List<Field>, container: DeclaredType?,
-            relationElement: VariableElement
-    ): android.arch.persistence.room.vo.Relation? {
-        val asTypeElement = MoreTypes.asTypeElement(
-                MoreElements.asVariable(relationElement).asType())
-
-        if (detectReferenceRecursion(asTypeElement)) {
-            return null
-        }
-
-        val annotation = MoreElements.getAnnotationMirror(relationElement, Relation::class.java)
-                .orNull()!!
-        val parentColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "parentColumn")
-                .getAsString("") ?: ""
-
-        val parentField = myFields.firstOrNull {
-            it.columnName == parentColumnInput
-        }
-        if (parentField == null) {
-            context.logger.e(relationElement,
-                    ProcessorErrors.relationCannotFindParentEntityField(
-                            entityName = element.qualifiedName.toString(),
-                            columnName = parentColumnInput,
-                            availableColumns = myFields.map { it.columnName }))
-            return null
-        }
-        // parse it as an entity.
-        val asMember = MoreTypes
-                .asMemberOf(context.processingEnv.typeUtils, container, relationElement)
-        if (asMember.kind == TypeKind.ERROR) {
-            context.logger.e(ProcessorErrors.CANNOT_FIND_TYPE, element)
-            return null
-        }
-        val declared = MoreTypes.asDeclared(asMember)
-        if (!declared.isCollection()) {
-            context.logger.e(relationElement, ProcessorErrors.RELATION_NOT_COLLECTION)
-            return null
-        }
-        val typeArg = declared.typeArguments.first()
-        if (typeArg.kind == TypeKind.ERROR) {
-            context.logger.e(MoreTypes.asTypeElement(typeArg), CANNOT_FIND_TYPE)
-            return null
-        }
-        val typeArgElement = MoreTypes.asTypeElement(typeArg)
-        val entityClassInput = AnnotationMirrors
-                .getAnnotationValue(annotation, "entity").toClassType()
-
-        // do we need to decide on the entity?
-        val inferEntity = (entityClassInput == null
-                || MoreTypes.isTypeOf(Any::class.java, entityClassInput))
-
-        val entity = if (inferEntity) {
-            EntityProcessor(context, typeArgElement, referenceStack).process()
-        } else {
-            EntityProcessor(context, MoreTypes.asTypeElement(entityClassInput),
-                    referenceStack).process()
-        }
-
-        // now find the field in the entity.
-        val entityColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "entityColumn")
-                .getAsString() ?: ""
-        val entityField = entity.fields.firstOrNull {
-            it.columnName == entityColumnInput
-        }
-
-        if (entityField == null) {
-            context.logger.e(relationElement,
-                    ProcessorErrors.relationCannotFindEntityField(
-                            entityName = entity.typeName.toString(),
-                            columnName = entityColumnInput,
-                            availableColumns = entity.fields.map { it.columnName }))
-            return null
-        }
-
-        val field = Field(
-                element = relationElement,
-                name = relationElement.simpleName.toString(),
-                type = context.processingEnv.typeUtils.asMemberOf(container, relationElement),
-                affinity = null,
-                parent = parent)
-
-        val projectionInput = AnnotationMirrors.getAnnotationValue(annotation, "projection")
-                .getAsStringList()
-        val projection = if (projectionInput.isEmpty()) {
-            // we need to infer the projection from inputs.
-            createRelationshipProjection(inferEntity, typeArg, entity, entityField, typeArgElement)
-        } else {
-            // make sure projection makes sense
-            validateRelationshipProjection(projectionInput, entity, relationElement)
-            projectionInput
-        }
-        // if types don't match, row adapter prints a warning
-        return android.arch.persistence.room.vo.Relation(
-                entity = entity,
-                pojoType = typeArg,
-                field = field,
-                parentField = parentField,
-                entityField = entityField,
-                projection = projection
-        )
-    }
-
-    private fun validateRelationshipProjection(
-            projectionInput: List<String>,
-            entity: Entity,
-            relationElement: VariableElement) {
-        val missingColumns = projectionInput.filterNot { columnName ->
-            entity.fields.any { columnName == it.columnName }
-        }
-        if (missingColumns.isNotEmpty()) {
-            context.logger.e(relationElement,
-                    ProcessorErrors.relationBadProject(entity.typeName.toString(),
-                            missingColumns, entity.fields.map { it.columnName }))
-        }
-    }
-
-    /**
-     * Create the projection column list based on the relationship args.
-     *
-     *  if entity field in the annotation is not specified, it is the method return type
-     *  if it is specified in the annotation:
-     *       still check the method return type, if the same, use it
-     *       if not, check to see if we can find a column Adapter, if so use the childField
-     *       last resort, try to parse it as a pojo to infer it.
-     */
-    private fun createRelationshipProjection(
-            inferEntity: Boolean,
-            typeArg: TypeMirror,
-            entity: Entity,
-            entityField: Field,
-            typeArgElement: TypeElement): List<String> {
-        return if (inferEntity || typeArg.typeName() == entity.typeName) {
-            entity.fields.map { it.columnName }
-        } else {
-            val columnAdapter = context.typeAdapterStore.findCursorValueReader(typeArg, null)
-            if (columnAdapter != null) {
-                // nice, there is a column adapter for this, assume single column response
-                listOf(entityField.name)
-            } else {
-                // last resort, it needs to be a pojo
-                val pojo = PojoProcessor(
-                        baseContext = context,
-                        element = typeArgElement,
-                        bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
-                        parent = parent,
-                        referenceStack = referenceStack).process()
-                pojo.fields.map { it.columnName }
-            }
-        }
-    }
-
-    private fun detectReferenceRecursion(typeElement: TypeElement): Boolean {
-        if (referenceStack.contains(typeElement.qualifiedName)) {
-            context.logger.e(
-                    typeElement,
-                    ProcessorErrors
-                            .RECURSIVE_REFERENCE_DETECTED
-                            .format(computeReferenceRecursionString(typeElement)))
-            return true
-        }
-        return false
-    }
-
-    private fun computeReferenceRecursionString(typeElement: TypeElement): String {
-        val recursiveTailTypeName = typeElement.qualifiedName
-
-        val referenceRecursionList = mutableListOf<Name>()
-        with(referenceRecursionList) {
-            add(recursiveTailTypeName)
-            addAll(referenceStack.toList().takeLastWhile { it != recursiveTailTypeName })
-            add(recursiveTailTypeName)
-        }
-
-        return referenceRecursionList.joinToString(" -> ")
-    }
-
-    private fun assignGetters(fields: List<Field>, getterCandidates: List<PojoMethod>) {
-        fields.forEach { field ->
-            assignGetter(field, getterCandidates)
-        }
-    }
-
-    private fun assignGetter(field: Field, getterCandidates: List<PojoMethod>) {
-        val success = chooseAssignment(field = field,
-                candidates = getterCandidates,
-                nameVariations = field.getterNameWithVariations,
-                getType = { method ->
-                    method.resolvedType.returnType
-                },
-                assignFromField = {
-                    field.getter = FieldGetter(
-                            name = field.name,
-                            type = field.type,
-                            callType = CallType.FIELD)
-                },
-                assignFromMethod = { match ->
-                    field.getter = FieldGetter(
-                            name = match.name,
-                            type = match.resolvedType.returnType,
-                            callType = CallType.METHOD)
-                },
-                reportAmbiguity = { matching ->
-                    context.logger.e(field.element,
-                            ProcessorErrors.tooManyMatchingGetters(field, matching))
-                })
-        context.checker.check(success, field.element, CANNOT_FIND_GETTER_FOR_FIELD)
-    }
-
-    private fun assignSetters(
-            fields: List<Field>,
-            setterCandidates: List<PojoMethod>,
-            constructor: Constructor?) {
-        fields.forEach { field ->
-            assignSetter(field, setterCandidates, constructor)
-        }
-    }
-
-    private fun assignSetter(
-            field: Field,
-            setterCandidates: List<PojoMethod>,
-            constructor: Constructor?) {
-        if (constructor != null && constructor.hasField(field)) {
-            field.setter = FieldSetter(field.name, field.type, CallType.CONSTRUCTOR)
-            return
-        }
-        val success = chooseAssignment(field = field,
-                candidates = setterCandidates,
-                nameVariations = field.setterNameWithVariations,
-                getType = { method ->
-                    method.resolvedType.parameterTypes.first()
-                },
-                assignFromField = {
-                    field.setter = FieldSetter(
-                            name = field.name,
-                            type = field.type,
-                            callType = CallType.FIELD)
-                },
-                assignFromMethod = { match ->
-                    val paramType = match.resolvedType.parameterTypes.first()
-                    field.setter = FieldSetter(
-                            name = match.name,
-                            type = paramType,
-                            callType = CallType.METHOD)
-                },
-                reportAmbiguity = { matching ->
-                    context.logger.e(field.element,
-                            ProcessorErrors.tooManyMatchingSetter(field, matching))
-                })
-        context.checker.check(success, field.element, CANNOT_FIND_SETTER_FOR_FIELD)
-    }
-
-    /**
-     * Finds a setter/getter from available list of methods.
-     * It returns true if assignment is successful, false otherwise.
-     * At worst case, it sets to the field as if it is accessible so that the rest of the
-     * compilation can continue.
-     */
-    private fun chooseAssignment(
-            field: Field,
-            candidates: List<PojoMethod>,
-            nameVariations: List<String>,
-            getType: (PojoMethod) -> TypeMirror,
-            assignFromField: () -> Unit,
-            assignFromMethod: (PojoMethod) -> Unit,
-            reportAmbiguity: (List<String>) -> Unit
-    ): Boolean {
-        if (field.element.hasAnyOf(PUBLIC)) {
-            assignFromField()
-            return true
-        }
-        val types = context.processingEnv.typeUtils
-
-        val matching = candidates
-                .filter {
-                    // b/69164099
-                    types.isAssignableWithoutVariance(getType(it), field.type)
-                            && (field.nameWithVariations.contains(it.name)
-                            || nameVariations.contains(it.name))
-                }
-                .groupBy {
-                    if (it.element.hasAnyOf(PUBLIC)) PUBLIC else PROTECTED
-                }
-        if (matching.isEmpty()) {
-            // we always assign to avoid NPEs in the rest of the compilation.
-            assignFromField()
-            // if field is not private, assume it works (if we are on the same package).
-            // if not, compiler will tell, we didn't have any better alternative anyways.
-            return !field.element.hasAnyOf(PRIVATE)
-        }
-        val match = verifyAndChooseOneFrom(matching[PUBLIC], reportAmbiguity)
-                ?: verifyAndChooseOneFrom(matching[PROTECTED], reportAmbiguity)
-        if (match == null) {
-            assignFromField()
-            return false
-        } else {
-            assignFromMethod(match)
-            return true
-        }
-    }
-
-    private fun verifyAndChooseOneFrom(
-            candidates: List<PojoMethod>?,
-            reportAmbiguity: (List<String>) -> Unit
-    ): PojoMethod? {
-        if (candidates == null) {
-            return null
-        }
-        if (candidates.size > 1) {
-            reportAmbiguity(candidates.map { it.name })
-        }
-        return candidates.first()
-    }
-
-    private data class FailedConstructor(
-            val method: ExecutableElement,
-            val params: List<String>,
-            val matches: List<Constructor.Param?>
-    ) {
-        fun log(): String {
-            val logPerParam = params.withIndex().joinToString(", ") {
-                "param:${it.value} -> matched field:" + (matches[it.index]?.log() ?: "unmatched")
-            }
-            return "$method -> [$logPerParam]"
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
deleted file mode 100644
index 9c3fecb..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ProcessorErrors.kt
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Delete
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.RawQuery
-import android.arch.persistence.room.Update
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.vo.CustomTypeConverter
-import android.arch.persistence.room.vo.Field
-import com.squareup.javapoet.TypeName
-
-object ProcessorErrors {
-    private fun String.trim(): String {
-        return this.trimIndent().replace("\n", " ")
-    }
-    val MISSING_QUERY_ANNOTATION = "Query methods must be annotated with ${Query::class.java}"
-    val MISSING_INSERT_ANNOTATION = "Insertion methods must be annotated with ${Insert::class.java}"
-    val MISSING_DELETE_ANNOTATION = "Deletion methods must be annotated with ${Delete::class.java}"
-    val MISSING_UPDATE_ANNOTATION = "Update methods must be annotated with ${Update::class.java}"
-    val MISSING_RAWQUERY_ANNOTATION = "RawQuery methods must be annotated with" +
-            " ${RawQuery::class.java}"
-    val INVALID_ON_CONFLICT_VALUE = "On conflict value must be one of @OnConflictStrategy values."
-    val INVALID_INSERTION_METHOD_RETURN_TYPE = "Methods annotated with @Insert can return either" +
-            " void, long, Long, long[], Long[] or List<Long>."
-    val TRANSACTION_REFERENCE_DOCS = "https://developer.android.com/reference/android/arch/" +
-            "persistence/room/Transaction.html"
-
-    fun insertionMethodReturnTypeMismatch(definedReturn: TypeName,
-                                          expectedReturnTypes: List<TypeName>): String {
-        return "Method returns $definedReturn but it should return one of the following: `" +
-                expectedReturnTypes.joinToString(", ") + "`. If you want to return the list of" +
-                " row ids from the query, your insertion method can receive only 1 parameter."
-    }
-
-    val ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION = "Abstract method in DAO must be annotated" +
-            " with ${Query::class.java} AND ${Insert::class.java}"
-    val INVALID_ANNOTATION_COUNT_IN_DAO_METHOD = "An abstract DAO method must be" +
-            " annotated with one and only one of the following annotations: " +
-            DaoProcessor.PROCESSED_ANNOTATIONS.joinToString(",") {
-        it.java.simpleName
-    }
-    val CANNOT_RESOLVE_RETURN_TYPE = "Cannot resolve return type for %s"
-    val CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS = "Cannot use unbound generics in query" +
-            " methods. It must be bound to a type through base Dao class."
-    val CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS = "Cannot use unbound generics in" +
-            " insertion methods. It must be bound to a type through base Dao class."
-    val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS = "Cannot use unbound fields in entities."
-    val CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES = "Cannot use unbound generics in Dao classes." +
-            " If you are trying to create a base DAO, create a normal class, extend it with type" +
-            " params then mark the subclass with @Dao."
-    val CANNOT_FIND_GETTER_FOR_FIELD = "Cannot find getter for field."
-    val CANNOT_FIND_SETTER_FOR_FIELD = "Cannot find setter for field."
-    val MISSING_PRIMARY_KEY = "An entity must have at least 1 field annotated with @PrimaryKey"
-    val AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT = "If a primary key is annotated with" +
-            " autoGenerate, its type must be int, Integer, long or Long."
-    val AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS = "When @PrimaryKey annotation is used on a" +
-            " field annotated with @Embedded, the embedded class should have only 1 field."
-
-    fun multiplePrimaryKeyAnnotations(primaryKeys: List<String>): String {
-        return """
-                You cannot have multiple primary keys defined in an Entity. If you
-                want to declare a composite primary key, you should use @Entity#primaryKeys and
-                not use @PrimaryKey. Defined Primary Keys:
-                ${primaryKeys.joinToString(", ")}""".trim()
-    }
-
-    fun primaryKeyColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
-        return "$columnName referenced in the primary key does not exists in the Entity." +
-                " Available column names:${allColumns.joinToString(", ")}"
-    }
-
-    val DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE = "Dao class must be an abstract class or" +
-            " an interface"
-    val DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE = "Database must be annotated with @Database"
-    val DAO_MUST_BE_ANNOTATED_WITH_DAO = "Dao class must be annotated with @Dao"
-
-    fun daoMustHaveMatchingConstructor(daoName: String, dbName: String): String {
-        return """
-                $daoName needs to have either an empty constructor or a constructor that takes
-                $dbName as its only parameter.
-                """.trim()
-    }
-
-    val ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY = "Entity class must be annotated with @Entity"
-    val DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES = "@Database annotation must specify list" +
-            " of entities"
-    val COLUMN_NAME_CANNOT_BE_EMPTY = "Column name cannot be blank. If you don't want to set it" +
-            ", just remove the @ColumnInfo annotation or use @ColumnInfo.INHERIT_FIELD_NAME."
-
-    val ENTITY_TABLE_NAME_CANNOT_BE_EMPTY = "Entity table name cannot be blank. If you don't want" +
-            " to set it, just remove the tableName property."
-
-    val CANNOT_BIND_QUERY_PARAMETER_INTO_STMT = "Query method parameters should either be a" +
-            " type that can be converted into a database column or a List / Array that contains" +
-            " such type. You can consider adding a Type Adapter for this."
-
-    val QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE = "Query/Insert method parameters cannot " +
-            "start with underscore (_)."
-
-    val CANNOT_FIND_QUERY_RESULT_ADAPTER = "Not sure how to convert a Cursor to this method's " +
-            "return type"
-
-    val INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
-            " @Insert but does not have any parameters to insert."
-
-    val DELETION_MISSING_PARAMS = "Method annotated with" +
-            " @Delete but does not have any parameters to delete."
-
-    val UPDATE_MISSING_PARAMS = "Method annotated with" +
-            " @Update but does not have any parameters to update."
-
-    val TRANSACTION_METHOD_MODIFIERS = "Method annotated with @Transaction must not be " +
-            "private, final, or abstract. It can be abstract only if the method is also" +
-            " annotated with @Query."
-
-    val TRANSACTION_MISSING_ON_RELATION = "The return value includes a Pojo with a @Relation." +
-            " It is usually desired to annotate this method with @Transaction to avoid" +
-            " possibility of inconsistent results between the Pojo and its relations. See " +
-            TRANSACTION_REFERENCE_DOCS + " for details."
-
-    val CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER = "Type of the parameter must be a class " +
-            "annotated with @Entity or a collection/array of it."
-
-    val DB_MUST_EXTEND_ROOM_DB = "Classes annotated with @Database should extend " +
-            RoomTypeNames.ROOM_DB
-
-    val LIVE_DATA_QUERY_WITHOUT_SELECT = "LiveData return type can only be used with SELECT" +
-            " queries."
-
-    val OBSERVABLE_QUERY_NOTHING_TO_OBSERVE = "Observable query return type (LiveData, Flowable" +
-            ", DataSource, DataSourceFactory etc) can only be used with SELECT queries that" +
-            " directly or indirectly (via @Relation, for example) access at least one table. For" +
-            " @RawQuery, you should specify the list of tables to be observed via the" +
-            " observedEntities field."
-
-    val RECURSIVE_REFERENCE_DETECTED = "Recursive referencing through @Embedded and/or @Relation " +
-            "detected: %s"
-
-    private val TOO_MANY_MATCHING_GETTERS = "Ambiguous getter for %s. All of the following " +
-            "match: %s. You can @Ignore the ones that you don't want to match."
-
-    fun tooManyMatchingGetters(field: Field, methodNames: List<String>): String {
-        return TOO_MANY_MATCHING_GETTERS.format(field, methodNames.joinToString(", "))
-    }
-
-    private val TOO_MANY_MATCHING_SETTERS = "Ambiguous setter for %s. All of the following " +
-            "match: %s. You can @Ignore the ones that you don't want to match."
-
-    fun tooManyMatchingSetter(field: Field, methodNames: List<String>): String {
-        return TOO_MANY_MATCHING_SETTERS.format(field, methodNames.joinToString(", "))
-    }
-
-    val CANNOT_FIND_COLUMN_TYPE_ADAPTER = "Cannot figure out how to save this field into" +
-            " database. You can consider adding a type converter for it."
-
-    val CANNOT_FIND_STMT_BINDER = "Cannot figure out how to bind this field into a statement."
-
-    val CANNOT_FIND_CURSOR_READER = "Cannot figure out how to read this field from a cursor."
-
-    private val MISSING_PARAMETER_FOR_BIND = "Each bind variable in the query must have a" +
-            " matching method parameter. Cannot find method parameters for %s."
-
-    fun missingParameterForBindVariable(bindVarName: List<String>): String {
-        return MISSING_PARAMETER_FOR_BIND.format(bindVarName.joinToString(", "))
-    }
-
-    private val UNUSED_QUERY_METHOD_PARAMETER = "Unused parameter%s: %s"
-    fun unusedQueryMethodParameter(unusedParams: List<String>): String {
-        return UNUSED_QUERY_METHOD_PARAMETER.format(
-                if (unusedParams.size > 1) "s" else "",
-                unusedParams.joinToString(","))
-    }
-
-    private val DUPLICATE_TABLES = "Table name \"%s\" is used by multiple entities: %s"
-    fun duplicateTableNames(tableName: String, entityNames: List<String>): String {
-        return DUPLICATE_TABLES.format(tableName, entityNames.joinToString(", "))
-    }
-
-    val DELETION_METHODS_MUST_RETURN_VOID_OR_INT = "Deletion methods must either return void or" +
-            " return int (the number of deleted rows)."
-
-    val UPDATE_METHODS_MUST_RETURN_VOID_OR_INT = "Update methods must either return void or" +
-            " return int (the number of updated rows)."
-
-    val DAO_METHOD_CONFLICTS_WITH_OTHERS = "Dao method has conflicts."
-
-    fun duplicateDao(dao: TypeName, methodNames: List<String>): String {
-        return """
-                All of these functions [${methodNames.joinToString(", ")}] return the same DAO
-                class [$dao].
-                A database can use a DAO only once so you should remove ${methodNames.size - 1} of
-                these conflicting DAO methods. If you are implementing any of these to fulfill an
-                interface, don't make it abstract, instead, implement the code that calls the
-                other one.
-                """.trim()
-    }
-
-    fun pojoMissingNonNull(pojoTypeName: TypeName, missingPojoFields: List<String>,
-                           allQueryColumns: List<String>): String {
-        return """
-        The columns returned by the query does not have the fields
-        [${missingPojoFields.joinToString(",")}] in $pojoTypeName even though they are
-        annotated as non-null or primitive.
-        Columns returned by the query: [${allQueryColumns.joinToString(",")}]
-        """.trim()
-    }
-
-    fun cursorPojoMismatch(pojoTypeName: TypeName,
-                           unusedColumns: List<String>, allColumns: List<String>,
-                           unusedFields: List<Field>, allFields: List<Field>): String {
-        val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) {
-            """
-                The query returns some columns [${unusedColumns.joinToString(", ")}] which are not
-                use by $pojoTypeName. You can use @ColumnInfo annotation on the fields to specify
-                the mapping.
-            """.trim()
-        } else {
-            ""
-        }
-        val unusedFieldsWarning = if (unusedFields.isNotEmpty()) {
-            """
-                $pojoTypeName has some fields
-                [${unusedFields.joinToString(", ") { it.columnName }}] which are not returned by the
-                query. If they are not supposed to be read from the result, you can mark them with
-                @Ignore annotation.
-            """.trim()
-        } else {
-            ""
-        }
-        return """
-            $unusedColumnsWarning
-            $unusedFieldsWarning
-            You can suppress this warning by annotating the method with
-            @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH).
-            Columns returned by the query: ${allColumns.joinToString(", ")}.
-            Fields in $pojoTypeName: ${allFields.joinToString(", ") { it.columnName }}.
-            """.trim()
-    }
-
-    val TYPE_CONVERTER_UNBOUND_GENERIC = "Cannot use unbound generics in Type Converters."
-    val TYPE_CONVERTER_BAD_RETURN_TYPE = "Invalid return type for a type converter."
-    val TYPE_CONVERTER_MUST_RECEIVE_1_PARAM = "Type converters must receive 1 parameter."
-    val TYPE_CONVERTER_EMPTY_CLASS = "Class is referenced as a converter but it does not have any" +
-            " converter methods."
-    val TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR = "Classes that are used as TypeConverters must" +
-            " have no-argument public constructors."
-    val TYPE_CONVERTER_MUST_BE_PUBLIC = "Type converters must be public."
-
-    fun duplicateTypeConverters(converters: List<CustomTypeConverter>): String {
-        return "Multiple methods define the same conversion. Conflicts with these:" +
-                " ${converters.joinToString(", ") { it.toString() }}"
-    }
-
-    // TODO must print field paths.
-    val POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME = "Field has non-unique column name."
-
-    fun pojoDuplicateFieldNames(columnName: String, fieldPaths: List<String>): String {
-        return "Multiple fields have the same columnName: $columnName." +
-                " Field names: ${fieldPaths.joinToString(", ")}."
-    }
-
-    fun embeddedPrimaryKeyIsDropped(entityQName: String, fieldName: String): String {
-        return "Primary key constraint on $fieldName is ignored when being merged into " +
-                entityQName
-    }
-
-    val INDEX_COLUMNS_CANNOT_BE_EMPTY = "List of columns in an index cannot be empty"
-
-    fun indexColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
-        return "$columnName referenced in the index does not exists in the Entity." +
-                " Available column names:${allColumns.joinToString(", ")}"
-    }
-
-    fun duplicateIndexInEntity(indexName: String): String {
-        return "There are multiple indices with name $indexName. This happen if you've declared" +
-                " the same index multiple times or different indices have the same name. See" +
-                " @Index documentation for details."
-    }
-
-    fun duplicateIndexInDatabase(indexName: String, indexPaths: List<String>): String {
-        return "There are multiple indices with name $indexName. You should rename " +
-                "${indexPaths.size - 1} of these to avoid the conflict:" +
-                "${indexPaths.joinToString(", ")}."
-    }
-
-    fun droppedEmbeddedFieldIndex(fieldPath: String, grandParent: String): String {
-        return "The index will be dropped when being merged into $grandParent" +
-                "($fieldPath). You must re-declare it in $grandParent if you want to index this" +
-                " field in $grandParent."
-    }
-
-    fun droppedEmbeddedIndex(entityName: String, fieldPath: String, grandParent: String): String {
-        return "Indices defined in $entityName will be dropped when it is merged into" +
-                " $grandParent ($fieldPath). You can re-declare them in $grandParent."
-    }
-
-    fun droppedSuperClassIndex(childEntity: String, superEntity: String): String {
-        return "Indices defined in $superEntity will NOT be re-used in $childEntity. If you want" +
-                " to inherit them, you must re-declare them in $childEntity." +
-                " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
-    }
-
-    fun droppedSuperClassFieldIndex(fieldName: String, childEntity: String,
-                                    superEntity: String): String {
-        return "Index defined on field `$fieldName` in $superEntity will NOT be re-used in" +
-                " $childEntity. " +
-                "If you want to inherit it, you must re-declare it in $childEntity." +
-                " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
-    }
-
-    val RELATION_NOT_COLLECTION = "Fields annotated with @Relation must be a List or Set."
-
-    fun relationCannotFindEntityField(entityName: String, columnName: String,
-                                      availableColumns: List<String>): String {
-        return "Cannot find the child entity column `$columnName` in $entityName." +
-                " Options: ${availableColumns.joinToString(", ")}"
-    }
-
-    fun relationCannotFindParentEntityField(entityName: String, columnName: String,
-                                            availableColumns: List<String>): String {
-        return "Cannot find the parent entity column `$columnName` in $entityName." +
-                " Options: ${availableColumns.joinToString(", ")}"
-    }
-
-    val RELATION_IN_ENTITY = "Entities cannot have relations."
-
-    val CANNOT_FIND_TYPE = "Cannot find type."
-
-    fun relationAffinityMismatch(parentColumn: String, childColumn: String,
-                                 parentAffinity: SQLTypeAffinity?,
-                                 childAffinity: SQLTypeAffinity?): String {
-        return """
-        The affinity of parent column ($parentColumn : $parentAffinity) does not match the type
-        affinity of the child column ($childColumn : $childAffinity).
-        """.trim()
-    }
-
-    val CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION = "A field can be annotated with only" +
-            " one of the following:" + PojoProcessor.PROCESSED_ANNOTATIONS.joinToString(",") {
-        it.java.simpleName
-    }
-
-    fun relationBadProject(entityQName: String, missingColumnNames: List<String>,
-                           availableColumnNames: List<String>): String {
-        return """
-        $entityQName does not have the following columns: ${missingColumnNames.joinToString(",")}.
-        Available columns are: ${availableColumnNames.joinToString(",")}
-        """.trim()
-    }
-
-    val MISSING_SCHEMA_EXPORT_DIRECTORY = "Schema export directory is not provided to the" +
-            " annotation processor so we cannot export the schema. You can either provide" +
-            " `room.schemaLocation` annotation processor argument OR set exportSchema to false."
-
-    val INVALID_FOREIGN_KEY_ACTION = "Invalid foreign key action. It must be one of the constants" +
-            " defined in ForeignKey.Action"
-
-    fun foreignKeyNotAnEntity(className: String): String {
-        return """
-        Classes referenced in Foreign Key annotations must be @Entity classes. $className is not
-        an entity
-        """.trim()
-    }
-
-    val FOREIGN_KEY_CANNOT_FIND_PARENT = "Cannot find parent entity class."
-
-    fun foreignKeyChildColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
-        return "($columnName) referenced in the foreign key does not exists in the Entity." +
-                " Available column names:${allColumns.joinToString(", ")}"
-    }
-
-    fun foreignKeyParentColumnDoesNotExist(parentEntity: String,
-                                           missingColumn: String,
-                                           allColumns: List<String>): String {
-        return "($missingColumn) does not exist in $parentEntity. Available columns are" +
-                " ${allColumns.joinToString(",")}"
-    }
-
-    val FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST = "Must specify at least 1 column name for the child"
-
-    val FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST = "Must specify at least 1 column name for the parent"
-
-    fun foreignKeyColumnNumberMismatch(
-            childColumns: List<String>, parentColumns: List<String>): String {
-        return """
-                Number of child columns in foreign key must match number of parent columns.
-                Child reference has ${childColumns.joinToString(",")} and parent reference has
-                ${parentColumns.joinToString(",")}
-               """.trim()
-    }
-
-    fun foreignKeyMissingParentEntityInDatabase(parentTable: String, childEntity: String): String {
-        return """
-                $parentTable table referenced in the foreign keys of $childEntity does not exist in
-                the database. Maybe you forgot to add the referenced entity in the entities list of
-                the @Database annotation?""".trim()
-    }
-
-    fun foreignKeyMissingIndexInParent(parentEntity: String, parentColumns: List<String>,
-                                       childEntity: String, childColumns: List<String>): String {
-        return """
-                $childEntity has a foreign key (${childColumns.joinToString(",")}) that references
-                $parentEntity (${parentColumns.joinToString(",")}) but $parentEntity does not have
-                a unique index on those columns nor the columns are its primary key.
-                SQLite requires having a unique constraint on referenced parent columns so you must
-                add a unique index to $parentEntity that has
-                (${parentColumns.joinToString(",")}) column(s).
-               """.trim()
-    }
-
-    fun foreignKeyMissingIndexInChildColumns(childColumns: List<String>): String {
-        return """
-                (${childColumns.joinToString(",")}) column(s) reference a foreign key but
-                they are not part of an index. This may trigger full table scans whenever parent
-                table is modified so you are highly advised to create an index that covers these
-                columns.
-               """.trim()
-    }
-
-    fun foreignKeyMissingIndexInChildColumn(childColumn: String): String {
-        return """
-                $childColumn column references a foreign key but it is not part of an index. This
-                may trigger full table scans whenever parent table is modified so you are highly
-                advised to create an index that covers this column.
-               """.trim()
-    }
-
-    fun shortcutEntityIsNotInDatabase(database: String, dao: String, entity: String): String {
-        return """
-                $dao is part of $database but this entity is not in the database. Maybe you forgot
-                to add $entity to the entities section of the @Database?
-                """.trim()
-    }
-
-    val MISSING_ROOM_GUAVA_ARTIFACT = "To use Guava features, you must add `guava`" +
-            " artifact from Room as a dependency. android.arch.persistence.room:guava:<version>"
-
-    val MISSING_ROOM_RXJAVA2_ARTIFACT = "To use RxJava2 features, you must add `rxjava2`" +
-            " artifact from Room as a dependency. android.arch.persistence.room:rxjava2:<version>"
-
-    fun ambigiousConstructor(
-            pojo: String, paramName: String, matchingFields: List<String>): String {
-        return """
-            Ambiguous constructor. The parameter ($paramName) in $pojo matches multiple fields:
-            [${matchingFields.joinToString(",")}]. If you don't want to use this constructor,
-            you can annotate it with @Ignore. If you want Room to use this constructor, you can
-            rename the parameters to exactly match the field name to fix the ambiguity.
-            """.trim()
-    }
-
-    val MISSING_POJO_CONSTRUCTOR = """
-            Entities and Pojos must have a usable public constructor. You can have an empty
-            constructor or a constructor whose parameters match the fields (by name and type).
-            """.trim()
-
-    val TOO_MANY_POJO_CONSTRUCTORS = """
-            Room cannot pick a constructor since multiple constructors are suitable. Try to annotate
-            unwanted constructors with @Ignore.
-            """.trim()
-
-    val TOO_MANY_POJO_CONSTRUCTORS_CHOOSING_NO_ARG = """
-            There are multiple good constructors and Room will pick the no-arg constructor.
-            You can use the @Ignore annotation to eliminate unwanted constructors.
-            """.trim()
-
-    val RELATION_CANNOT_BE_CONSTRUCTOR_PARAMETER = """
-            Fields annotated with @Relation cannot be constructor parameters. These values are
-            fetched after the object is constructed.
-            """.trim()
-
-    val PAGING_SPECIFY_DATA_SOURCE_TYPE = "For now, Room only supports PositionalDataSource class."
-
-    fun primaryKeyNull(field: String): String {
-        return "You must annotate primary keys with @NonNull. \"$field\" is nullable. SQLite " +
-                "considers this a " +
-                "bug and Room does not allow it. See SQLite docs for details: " +
-                "https://www.sqlite.org/lang_createtable.html"
-    }
-
-    val INVALID_COLUMN_NAME = "Invalid column name. Room does not allow using ` or \" in column" +
-            " names"
-
-    val INVALID_TABLE_NAME = "Invalid table name. Room does not allow using ` or \" in table names"
-
-    val RAW_QUERY_BAD_PARAMS = "RawQuery methods should have 1 and only 1 parameter with type" +
-            " String or SupportSQLiteQuery"
-
-    val RAW_QUERY_BAD_RETURN_TYPE = "RawQuery methods must return a non-void type."
-
-    fun rawQueryBadEntity(typeName: TypeName): String {
-        return """
-            observedEntities field in RawQuery must either reference a class that is annotated
-            with @Entity or it should reference a Pojo that either contains @Embedded fields that
-            are annotated with @Entity or @Relation fields.
-            $typeName does not have these properties, did you mean another class?
-            """.trim()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt
deleted file mode 100644
index e0728d7..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryMethodProcessor.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.SkipQueryVerification
-import android.arch.persistence.room.Transaction
-import android.arch.persistence.room.ext.KotlinMetadataProcessor
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.parser.QueryType
-import android.arch.persistence.room.parser.SqlParser
-import android.arch.persistence.room.solver.query.result.LiveDataQueryResultBinder
-import android.arch.persistence.room.solver.query.result.PojoRowAdapter
-import android.arch.persistence.room.verifier.DatabaseVerificaitonErrors
-import android.arch.persistence.room.verifier.DatabaseVerifier
-import android.arch.persistence.room.vo.QueryMethod
-import android.arch.persistence.room.vo.QueryParameter
-import android.arch.persistence.room.vo.Warning
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.TypeName
-import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
-import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeKind
-
-class QueryMethodProcessor(
-        baseContext: Context,
-        val containing: DeclaredType,
-        val executableElement: ExecutableElement,
-        val dbVerifier: DatabaseVerifier? = null
-) : KotlinMetadataProcessor {
-    val context = baseContext.fork(executableElement)
-
-    // for kotlin metadata
-    override val processingEnv: ProcessingEnvironment
-        get() = context.processingEnv
-
-    private val classMetadata =
-            try {
-                containing.asElement().kotlinMetadata
-            } catch (throwable: Throwable) {
-                context.logger.d(executableElement,
-                        "failed to read get kotlin metadata from %s", executableElement)
-            } as? KotlinClassMetadata
-
-    fun process(): QueryMethod {
-        val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
-        val executableType = MoreTypes.asExecutable(asMember)
-
-        val annotation = MoreElements.getAnnotationMirror(executableElement,
-                Query::class.java).orNull()
-        context.checker.check(annotation != null, executableElement,
-                ProcessorErrors.MISSING_QUERY_ANNOTATION)
-
-        val query = if (annotation != null) {
-            val query = SqlParser.parse(
-                    AnnotationMirrors.getAnnotationValue(annotation, "value").value.toString())
-            context.checker.check(query.errors.isEmpty(), executableElement,
-                    query.errors.joinToString("\n"))
-            if (!executableElement.hasAnnotation(SkipQueryVerification::class)) {
-                query.resultInfo = dbVerifier?.analyze(query.original)
-            }
-            if (query.resultInfo?.error != null) {
-                context.logger.e(executableElement,
-                        DatabaseVerificaitonErrors.cannotVerifyQuery(query.resultInfo!!.error!!))
-            }
-
-            context.checker.check(executableType.returnType.kind != TypeKind.ERROR,
-                    executableElement, ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE,
-                    executableElement)
-            query
-        } else {
-            ParsedQuery.MISSING
-        }
-
-        val returnTypeName = TypeName.get(executableType.returnType)
-        context.checker.notUnbound(returnTypeName, executableElement,
-                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
-
-        if (query.type == QueryType.DELETE) {
-            context.checker.check(
-                    returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
-                    executableElement,
-                    ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
-            )
-        }
-        val resultBinder = context.typeAdapterStore
-                .findQueryResultBinder(executableType.returnType, query)
-        context.checker.check(resultBinder.adapter != null || query.type != QueryType.SELECT,
-                executableElement, ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
-        if (resultBinder is LiveDataQueryResultBinder) {
-            context.checker.check(query.type == QueryType.SELECT, executableElement,
-                    ProcessorErrors.LIVE_DATA_QUERY_WITHOUT_SELECT)
-        }
-
-        val inTransaction = when (query.type) {
-            QueryType.SELECT -> executableElement.hasAnnotation(Transaction::class)
-            else -> true
-        }
-
-        if (query.type == QueryType.SELECT && !inTransaction) {
-            // put a warning if it is has relations and not annotated w/ transaction
-            resultBinder.adapter?.rowAdapter?.let { rowAdapter ->
-                if (rowAdapter is PojoRowAdapter
-                        && rowAdapter.relationCollectors.isNotEmpty()) {
-                    context.logger.w(Warning.RELATION_QUERY_WITHOUT_TRANSACTION,
-                            executableElement, ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
-                }
-            }
-        }
-        val kotlinParameterNames = classMetadata?.getParameterNames(executableElement)
-
-        val parameters = executableElement.parameters
-                .mapIndexed { index, variableElement ->
-                    QueryParameterProcessor(
-                            baseContext = context,
-                            containing = containing,
-                            element = variableElement,
-                            sqlName = kotlinParameterNames?.getOrNull(index)).process()
-                }
-        val queryMethod = QueryMethod(
-                element = executableElement,
-                query = query,
-                name = executableElement.simpleName.toString(),
-                returnType = executableType.returnType,
-                parameters = parameters,
-                inTransaction = inTransaction,
-                queryResultBinder = resultBinder)
-
-        val missing = queryMethod.sectionToParamMapping
-                .filter { it.second == null }
-                .map { it.first.text }
-        if (missing.isNotEmpty()) {
-            context.logger.e(executableElement,
-                    ProcessorErrors.missingParameterForBindVariable(missing))
-        }
-
-        val unused = queryMethod.parameters.filterNot { param ->
-            queryMethod.sectionToParamMapping.any { it.second == param }
-        }.map(QueryParameter::sqlName)
-
-        if (unused.isNotEmpty()) {
-            context.logger.e(executableElement, ProcessorErrors.unusedQueryMethodParameter(unused))
-        }
-        return queryMethod
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryParameterProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryParameterProcessor.kt
deleted file mode 100644
index 0a9016f..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/QueryParameterProcessor.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.vo.QueryParameter
-import com.google.auto.common.MoreTypes
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.DeclaredType
-
-class QueryParameterProcessor(
-        baseContext: Context,
-        val containing: DeclaredType,
-        val element: VariableElement,
-        private val sqlName: String? = null) {
-    val context = baseContext.fork(element)
-    fun process(): QueryParameter {
-        val asMember = MoreTypes.asMemberOf(context.processingEnv.typeUtils, containing, element)
-        val parameterAdapter = context.typeAdapterStore.findQueryParameterAdapter(asMember)
-        context.checker.check(parameterAdapter != null, element,
-                ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
-
-        val name = element.simpleName.toString()
-        context.checker.check(!name.startsWith("_"), element,
-                ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
-        return QueryParameter(
-                name = name,
-                sqlName = sqlName ?: name,
-                type = asMember,
-                queryParamAdapter = parameterAdapter)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
deleted file mode 100644
index b26ff6e..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessor.kt
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2018 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.processor
-
-import android.arch.persistence.room.RawQuery
-import android.arch.persistence.room.Transaction
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.toListOfClassTypes
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.SqlParser
-import android.arch.persistence.room.vo.RawQueryMethod
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-
-class RawQueryMethodProcessor(
-        baseContext: Context,
-        val containing: DeclaredType,
-        val executableElement: ExecutableElement) {
-    val context = baseContext.fork(executableElement)
-    fun process(): RawQueryMethod {
-        val types = context.processingEnv.typeUtils
-        val asMember = types.asMemberOf(containing, executableElement)
-        val executableType = MoreTypes.asExecutable(asMember)
-
-        val annotation = MoreElements.getAnnotationMirror(executableElement,
-                RawQuery::class.java).orNull()
-        context.checker.check(annotation != null, executableElement,
-                ProcessorErrors.MISSING_RAWQUERY_ANNOTATION)
-
-        val returnTypeName = TypeName.get(executableType.returnType)
-        context.checker.notUnbound(returnTypeName, executableElement,
-                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
-        val observedTableNames = processObservedTables()
-        val query = SqlParser.rawQueryForTables(observedTableNames)
-        // build the query but don't calculate result info since we just guessed it.
-        val resultBinder = context.typeAdapterStore
-                .findQueryResultBinder(executableType.returnType, query)
-
-        val runtimeQueryParam = findRuntimeQueryParameter()
-        val inTransaction = executableElement.hasAnnotation(Transaction::class)
-        val rawQueryMethod = RawQueryMethod(
-                element = executableElement,
-                name = executableElement.simpleName.toString(),
-                observedTableNames = observedTableNames,
-                returnType = executableType.returnType,
-                runtimeQueryParam = runtimeQueryParam,
-                inTransaction = inTransaction,
-                queryResultBinder = resultBinder
-        )
-        context.checker.check(rawQueryMethod.returnsValue, executableElement,
-                ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE)
-        return rawQueryMethod
-    }
-
-    private fun processObservedTables(): Set<String> {
-        val annotation = MoreElements
-                .getAnnotationMirror(executableElement,
-                        android.arch.persistence.room.RawQuery::class.java)
-                .orNull() ?: return emptySet()
-        val entityList = AnnotationMirrors.getAnnotationValue(annotation, "observedEntities")
-        return entityList
-                .toListOfClassTypes()
-                .map {
-                    MoreTypes.asTypeElement(it)
-                }
-                .flatMap {
-                    if (it.hasAnnotation(android.arch.persistence.room.Entity::class)) {
-                        val entity = EntityProcessor(
-                                baseContext = context,
-                                element = it
-                        ).process()
-                        arrayListOf(entity.tableName)
-                    } else {
-                        val pojo = PojoProcessor(
-                                baseContext = context,
-                                element = it,
-                                bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
-                                parent = null
-                        ).process()
-                        val tableNames = pojo.accessedTableNames()
-                        // if it is empty, report error as it does not make sense
-                        if (tableNames.isEmpty()) {
-                            context.logger.e(executableElement,
-                                    ProcessorErrors.rawQueryBadEntity(it.asType().typeName()))
-                        }
-                        tableNames
-                    }
-                }.toSet()
-    }
-
-    private fun findRuntimeQueryParameter(): RawQueryMethod.RuntimeQueryParameter? {
-        val types = context.processingEnv.typeUtils
-        if (executableElement.parameters.size == 1 && !executableElement.isVarArgs) {
-            val param = MoreTypes.asMemberOf(
-                    types,
-                    containing,
-                    executableElement.parameters[0])
-            val elementUtils = context.processingEnv.elementUtils
-            val supportQueryType = elementUtils
-                    .getTypeElement(SupportDbTypeNames.QUERY.toString()).asType()
-            val isSupportSql = types.isAssignable(param, supportQueryType)
-            if (isSupportSql) {
-                return RawQueryMethod.RuntimeQueryParameter(
-                        paramName = executableElement.parameters[0].simpleName.toString(),
-                        type = supportQueryType.typeName())
-            }
-            val stringType = elementUtils.getTypeElement("java.lang.String").asType()
-            val isString = types.isAssignable(param, stringType)
-            if (isString) {
-                return RawQueryMethod.RuntimeQueryParameter(
-                        paramName = executableElement.parameters[0].simpleName.toString(),
-                        type = stringType.typeName())
-            }
-        }
-        context.logger.e(executableElement, ProcessorErrors.RAW_QUERY_BAD_PARAMS)
-        return null
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessor.kt
deleted file mode 100644
index c13b818..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessor.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.ShortcutQueryParameter
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import javax.lang.model.element.AnnotationMirror
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeMirror
-import kotlin.reflect.KClass
-
-/**
- * Common functionality for shortcut method processors
- */
-class ShortcutMethodProcessor(baseContext: Context,
-                              val containing: DeclaredType,
-                              val executableElement: ExecutableElement) {
-    val context = baseContext.fork(executableElement)
-    private val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
-    private val executableType = MoreTypes.asExecutable(asMember)
-
-    fun extractAnnotation(klass: KClass<out Annotation>,
-                          errorMsg: String): AnnotationMirror? {
-        val annotation = MoreElements.getAnnotationMirror(executableElement,
-                klass.java).orNull()
-        context.checker.check(annotation != null, executableElement, errorMsg)
-        return annotation
-    }
-
-    fun extractReturnType(): TypeMirror {
-        return executableType.returnType
-    }
-
-    fun extractParams(
-            missingParamError: String
-    ): Pair<Map<String, Entity>, List<ShortcutQueryParameter>> {
-        val params = executableElement.parameters
-                .map { ShortcutParameterProcessor(
-                        baseContext = context,
-                        containing = containing,
-                        element = it).process() }
-        context.checker.check(params.isNotEmpty(), executableElement, missingParamError)
-        val entities = params
-                .filter { it.entityType != null }
-                .associateBy({ it.name }, {
-                    EntityProcessor(
-                            baseContext = context,
-                            element = MoreTypes.asTypeElement(it.entityType)).process()
-                })
-        return Pair(entities, params)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutParameterProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutParameterProcessor.kt
deleted file mode 100644
index 7fd6859..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/ShortcutParameterProcessor.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.ext.extendsBound
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.vo.ShortcutQueryParameter
-import com.google.auto.common.MoreTypes
-import javax.lang.model.element.TypeElement
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.ArrayType
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeMirror
-import javax.lang.model.util.ElementFilter
-
-/**
- * Processes parameters of methods that are annotated with Insert, Delete.
- */
-class ShortcutParameterProcessor(baseContext: Context,
-                                 val containing: DeclaredType,
-                                 val element: VariableElement) {
-    val context = baseContext.fork(element)
-    fun process(): ShortcutQueryParameter {
-        val asMember = MoreTypes.asMemberOf(context.processingEnv.typeUtils, containing, element)
-        val name = element.simpleName.toString()
-        context.checker.check(!name.startsWith("_"), element,
-                ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
-
-        val (entityType, isMultiple) = extractEntityType(asMember)
-        context.checker.check(entityType != null, element,
-                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER)
-
-        return ShortcutQueryParameter(
-                name = name,
-                type = asMember,
-                entityType = entityType,
-                isMultiple = isMultiple
-        )
-    }
-
-    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-    fun extractEntityType(typeMirror: TypeMirror): Pair<TypeMirror?, Boolean> {
-
-        val elementUtils = context.processingEnv.elementUtils
-        val typeUtils = context.processingEnv.typeUtils
-
-        fun verifyAndPair(entityType: TypeMirror, isMultiple: Boolean): Pair<TypeMirror?, Boolean> {
-            if (!MoreTypes.isType(entityType)) {
-                // kotlin may generate ? extends T so we should reduce it.
-                val boundedVar = entityType.extendsBound()
-                return boundedVar?.let {
-                    verifyAndPair(boundedVar, isMultiple)
-                } ?: Pair(null, isMultiple)
-            }
-            val entityElement = MoreTypes.asElement(entityType)
-            return if (entityElement.hasAnnotation(Entity::class)) {
-                Pair(entityType, isMultiple)
-            } else {
-                Pair(null, isMultiple)
-            }
-        }
-
-        fun extractEntityTypeFromIterator(iterableType: DeclaredType): TypeMirror {
-            ElementFilter.methodsIn(elementUtils
-                    .getAllMembers(typeUtils.asElement(iterableType) as TypeElement)).forEach {
-                if (it.simpleName.toString() == "iterator") {
-                    return MoreTypes.asDeclared(MoreTypes.asExecutable(
-                            typeUtils.asMemberOf(iterableType, it)).returnType)
-                            .typeArguments.first()
-                }
-            }
-            throw IllegalArgumentException("iterator() not found in Iterable $iterableType")
-        }
-
-        val iterableType = typeUtils.erasure(elementUtils
-                .getTypeElement("java.lang.Iterable").asType())
-        if (typeUtils.isAssignable(typeMirror, iterableType)) {
-            val declared = MoreTypes.asDeclared(typeMirror)
-            val entity = extractEntityTypeFromIterator(declared)
-            return verifyAndPair(entity, true)
-        }
-        if (typeMirror is ArrayType) {
-            val entity = typeMirror.componentType
-            return verifyAndPair(entity, true)
-        }
-        return verifyAndPair(typeMirror, false)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/SuppressWarningProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/SuppressWarningProcessor.kt
deleted file mode 100644
index 9edf2f9..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/SuppressWarningProcessor.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.vo.Warning
-import com.google.auto.common.AnnotationMirrors
-import com.google.auto.common.MoreElements
-import javax.lang.model.element.AnnotationValue
-import javax.lang.model.element.Element
-import javax.lang.model.util.SimpleAnnotationValueVisitor6
-
-/**
- * A visitor that reads SuppressWarnings annotations and keeps the ones we know about.
- */
-object SuppressWarningProcessor {
-
-    fun getSuppressedWarnings(element: Element): Set<Warning> {
-        val annotation = MoreElements.getAnnotationMirror(element,
-                SuppressWarnings::class.java).orNull()
-        return if (annotation == null) {
-            emptySet<Warning>()
-        } else {
-            val value = AnnotationMirrors.getAnnotationValue(annotation, "value")
-            if (value == null) {
-                emptySet<Warning>()
-            } else {
-                VISITOR.visit(value)
-            }
-        }
-    }
-
-    private object VISITOR : SimpleAnnotationValueVisitor6<Set<Warning>, String>() {
-        override fun visitArray(values: List<AnnotationValue>?, elementName: String?
-        ): Set<Warning> {
-            return values?.map {
-                Warning.fromPublicKey(it.value.toString())
-            }?.filterNotNull()?.toSet() ?: emptySet()
-        }
-
-        override fun defaultAction(o: Any?, p: String?): Set<Warning> {
-            return emptySet()
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessor.kt
deleted file mode 100644
index d35509d..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessor.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.ext.findKotlinDefaultImpl
-import android.arch.persistence.room.ext.hasAnyOf
-import android.arch.persistence.room.vo.TransactionMethod
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier.ABSTRACT
-import javax.lang.model.element.Modifier.DEFAULT
-import javax.lang.model.element.Modifier.FINAL
-import javax.lang.model.element.Modifier.PRIVATE
-import javax.lang.model.type.DeclaredType
-
-class TransactionMethodProcessor(baseContext: Context,
-                                 val containing: DeclaredType,
-                                 val executableElement: ExecutableElement) {
-
-    val context = baseContext.fork(executableElement)
-
-    fun process(): TransactionMethod {
-        val kotlinDefaultImpl =
-                executableElement.findKotlinDefaultImpl(context.processingEnv.typeUtils)
-        context.checker.check(
-                !executableElement.hasAnyOf(PRIVATE, FINAL)
-                        && (!executableElement.hasAnyOf(ABSTRACT) || kotlinDefaultImpl != null),
-                executableElement, ProcessorErrors.TRANSACTION_METHOD_MODIFIERS)
-
-        val callType = when {
-            executableElement.hasAnyOf(DEFAULT) ->
-                TransactionMethod.CallType.DEFAULT_JAVA8
-            kotlinDefaultImpl != null ->
-                TransactionMethod.CallType.DEFAULT_KOTLIN
-            else ->
-                TransactionMethod.CallType.CONCRETE
-        }
-
-        return TransactionMethod(
-                element = executableElement,
-                name = executableElement.simpleName.toString(),
-                callType = callType)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessor.kt
deleted file mode 100644
index 27211ce..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessor.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.OnConflictStrategy.IGNORE
-import android.arch.persistence.room.OnConflictStrategy.REPLACE
-import android.arch.persistence.room.Update
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.vo.UpdateMethod
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-
-class UpdateMethodProcessor(
-        baseContext: Context,
-        val containing: DeclaredType,
-        val executableElement: ExecutableElement) {
-    val context = baseContext.fork(executableElement)
-
-    fun process(): UpdateMethod {
-        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
-        val annotation = delegate
-                .extractAnnotation(Update::class, ProcessorErrors.MISSING_UPDATE_ANNOTATION)
-
-        val onConflict = OnConflictProcessor.extractFrom(annotation)
-        context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE,
-                executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
-
-        val returnTypeName = delegate.extractReturnType().typeName()
-        context.checker.check(
-                returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
-                executableElement,
-                ProcessorErrors.UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
-        )
-
-        val (entities, params) = delegate.extractParams(
-                missingParamError = ProcessorErrors
-                        .UPDATE_MISSING_PARAMS
-        )
-
-        return UpdateMethod(
-                element = delegate.executableElement,
-                name = delegate.executableElement.simpleName.toString(),
-                entities = entities,
-                onConflictStrategy = onConflict,
-                returnCount = returnTypeName == TypeName.INT,
-                parameters = params
-        )
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/cache/Cache.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/cache/Cache.kt
deleted file mode 100644
index 0ef5dde..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/processor/cache/Cache.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("AddVarianceModifier")
-
-package android.arch.persistence.room.processor.cache
-
-import android.arch.persistence.room.processor.FieldProcessor
-import android.arch.persistence.room.vo.EmbeddedField
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.Pojo
-import android.arch.persistence.room.vo.Warning
-import java.util.LinkedHashSet
-import javax.lang.model.element.Element
-import javax.lang.model.type.TypeMirror
-
-/**
- * A cache key can be used to avoid re-processing elements.
- * <p>
- * Each context has a cache variable that uses the same backing storage as the Root Context but
- * adds current adapters and warning suppression list to the key.
- */
-class Cache(val parent: Cache?, val converters: LinkedHashSet<TypeMirror>,
-            val suppressedWarnings: Set<Warning>) {
-    val entities: Bucket<EntityKey, Entity> = Bucket(parent?.entities)
-    val pojos: Bucket<PojoKey, Pojo> = Bucket(parent?.pojos)
-
-    inner class Bucket<K, T>(source: Bucket<K, T>?) {
-        private val entries: MutableMap<FullKey<K>, T> = source?.entries ?: mutableMapOf()
-        fun get(key: K, calculate: () -> T): T {
-            val fullKey = FullKey(converters, suppressedWarnings, key)
-            return entries.getOrPut(fullKey, {
-                calculate()
-            })
-        }
-    }
-
-    /**
-     * Key for Entity cache
-     */
-    data class EntityKey(val element: Element)
-
-    /**
-     * Key for Pojo cache
-     */
-    data class PojoKey(
-            val element: Element,
-            val scope: FieldProcessor.BindingScope,
-            val parent: EmbeddedField?)
-
-    /**
-     * Internal key representation with adapters & warnings included.
-     * <p>
-     * Converters are kept in a linked set since the order is important for the TypeAdapterStore.
-     */
-    private data class FullKey<T>(
-            val converters: LinkedHashSet<TypeMirror>,
-            val suppressedWarnings: Set<Warning>,
-            val key: T)
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/CodeGenScope.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/CodeGenScope.kt
deleted file mode 100644
index 59c2283..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/CodeGenScope.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver
-
-import com.google.common.annotations.VisibleForTesting
-import com.squareup.javapoet.CodeBlock
-import android.arch.persistence.room.writer.ClassWriter
-/**
- * Defines a code generation scope where we can provide temporary variables, global variables etc
- */
-class CodeGenScope(val writer: ClassWriter) {
-    private var tmpVarIndices = mutableMapOf<String, Int>()
-    private var builder: CodeBlock.Builder? = null
-    companion object {
-        const val TMP_VAR_DEFAULT_PREFIX = "_tmp"
-        const val CLASS_PROPERTY_PREFIX = "__"
-        @VisibleForTesting
-        fun _tmpVar(index: Int) = _tmpVar(TMP_VAR_DEFAULT_PREFIX, index)
-        fun _tmpVar(prefix: String, index: Int) = "$prefix${if (index == 0) "" else "_$index"}"
-    }
-
-    fun builder(): CodeBlock.Builder {
-        if (builder == null) {
-            builder = CodeBlock.builder()
-        }
-        return builder!!
-    }
-
-    fun getTmpVar(): String {
-        return getTmpVar(TMP_VAR_DEFAULT_PREFIX)
-    }
-
-    fun getTmpVar(prefix: String): String {
-        if (!prefix.startsWith("_")) {
-            throw IllegalArgumentException("tmp variable prefixes should start with _")
-        }
-        if (prefix.startsWith(CLASS_PROPERTY_PREFIX)) {
-            throw IllegalArgumentException("cannot use $CLASS_PROPERTY_PREFIX for tmp variables")
-        }
-        val index = tmpVarIndices.getOrElse(prefix) { 0 }
-        val result = _tmpVar(prefix, index)
-        tmpVarIndices.put(prefix, index + 1)
-        return result
-    }
-
-    fun generate() = builder().build().toString()
-
-    /**
-     * copies all variable indices but excludes generated code.
-     */
-    fun fork(): CodeGenScope {
-        val forked = CodeGenScope(writer)
-        forked.tmpVarIndices.putAll(tmpVarIndices)
-        return forked
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/ObservableQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/ObservableQueryResultBinderProvider.kt
deleted file mode 100644
index 4bd52cc..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/ObservableQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver
-
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.query.result.QueryResultAdapter
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeMirror
-
-/**
- * Binder provider class that has common functionality for observables.
- */
-abstract class ObservableQueryResultBinderProvider(val context: Context)
-    : QueryResultBinderProvider {
-    protected abstract fun extractTypeArg(declared: DeclaredType): TypeMirror
-    protected abstract fun create(typeArg: TypeMirror,
-                                  resultAdapter: QueryResultAdapter?,
-                                  tableNames: Set<String>): QueryResultBinder
-
-    final override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
-        val typeArg = extractTypeArg(declared)
-        val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query)
-        val tableNames = ((adapter?.accessedTableNames() ?: emptyList()) +
-                query.tables.map { it.name }).toSet()
-        if (tableNames.isEmpty()) {
-            context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-        }
-        return create(
-                typeArg = typeArg,
-                resultAdapter = adapter,
-                tableNames = tableNames
-        )
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/QueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/QueryResultBinderProvider.kt
deleted file mode 100644
index ccc3597..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/QueryResultBinderProvider.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver
-
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import javax.lang.model.type.DeclaredType
-
-interface QueryResultBinderProvider {
-    fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder
-    fun matches(declared: DeclaredType): Boolean
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt
deleted file mode 100644
index fc5c4d4..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/TypeAdapterStore.kt
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.GuavaBaseTypeNames
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.EntityProcessor
-import android.arch.persistence.room.processor.FieldProcessor
-import android.arch.persistence.room.processor.PojoProcessor
-import android.arch.persistence.room.solver.binderprovider.CursorQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.DataSourceQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.FlowableQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.InstantQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.LiveDataQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.GuavaListenableFutureQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.RxMaybeQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.RxSingleQueryResultBinderProvider
-import android.arch.persistence.room.solver.query.parameter.ArrayQueryParameterAdapter
-import android.arch.persistence.room.solver.query.parameter.BasicQueryParameterAdapter
-import android.arch.persistence.room.solver.query.parameter.CollectionQueryParameterAdapter
-import android.arch.persistence.room.solver.query.parameter.QueryParameterAdapter
-import android.arch.persistence.room.solver.query.result.ArrayQueryResultAdapter
-import android.arch.persistence.room.solver.query.result.EntityRowAdapter
-import android.arch.persistence.room.solver.query.result.GuavaOptionalQueryResultAdapter
-import android.arch.persistence.room.solver.query.result.InstantQueryResultBinder
-import android.arch.persistence.room.solver.query.result.ListQueryResultAdapter
-import android.arch.persistence.room.solver.query.result.OptionalQueryResultAdapter
-import android.arch.persistence.room.solver.query.result.PojoRowAdapter
-import android.arch.persistence.room.solver.query.result.QueryResultAdapter
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import android.arch.persistence.room.solver.query.result.RowAdapter
-import android.arch.persistence.room.solver.query.result.SingleColumnRowAdapter
-import android.arch.persistence.room.solver.query.result.SingleEntityQueryResultAdapter
-import android.arch.persistence.room.solver.types.BoxedBooleanToBoxedIntConverter
-import android.arch.persistence.room.solver.types.BoxedPrimitiveColumnTypeAdapter
-import android.arch.persistence.room.solver.types.ByteArrayColumnTypeAdapter
-import android.arch.persistence.room.solver.types.ColumnTypeAdapter
-import android.arch.persistence.room.solver.types.CompositeAdapter
-import android.arch.persistence.room.solver.types.CompositeTypeConverter
-import android.arch.persistence.room.solver.types.CursorValueReader
-import android.arch.persistence.room.solver.types.NoOpConverter
-import android.arch.persistence.room.solver.types.PrimitiveBooleanToIntConverter
-import android.arch.persistence.room.solver.types.PrimitiveColumnTypeAdapter
-import android.arch.persistence.room.solver.types.StatementValueBinder
-import android.arch.persistence.room.solver.types.StringColumnTypeAdapter
-import android.arch.persistence.room.solver.types.TypeConverter
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.annotations.VisibleForTesting
-import java.util.LinkedList
-import javax.lang.model.type.ArrayType
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-/**
- * Holds all type adapters and can create on demand composite type adapters to convert a type into a
- * database column.
- */
-class TypeAdapterStore private constructor(
-        val context: Context,
-        /**
-         * first type adapter has the highest priority
-         */
-        private val columnTypeAdapters: List<ColumnTypeAdapter>,
-        /**
-         * first converter has the highest priority
-         */
-        private val typeConverters: List<TypeConverter>) {
-
-    companion object {
-        fun copy(context: Context, store: TypeAdapterStore): TypeAdapterStore {
-            return TypeAdapterStore(context = context,
-                    columnTypeAdapters = store.columnTypeAdapters,
-                    typeConverters = store.typeConverters)
-        }
-
-        fun create(context: Context, @VisibleForTesting vararg extras: Any): TypeAdapterStore {
-            val adapters = arrayListOf<ColumnTypeAdapter>()
-            val converters = arrayListOf<TypeConverter>()
-
-            fun addAny(extra: Any?) {
-                when (extra) {
-                    is TypeConverter -> converters.add(extra)
-                    is ColumnTypeAdapter -> adapters.add(extra)
-                    is List<*> -> extra.forEach(::addAny)
-                    else -> throw IllegalArgumentException("unknown extra $extra")
-                }
-            }
-
-            extras.forEach(::addAny)
-            fun addTypeConverter(converter: TypeConverter) {
-                converters.add(converter)
-            }
-
-            fun addColumnAdapter(adapter: ColumnTypeAdapter) {
-                adapters.add(adapter)
-            }
-
-            val primitives = PrimitiveColumnTypeAdapter
-                    .createPrimitiveAdapters(context.processingEnv)
-            primitives.forEach(::addColumnAdapter)
-            BoxedPrimitiveColumnTypeAdapter
-                    .createBoxedPrimitiveAdapters(context.processingEnv, primitives)
-                    .forEach(::addColumnAdapter)
-            addColumnAdapter(StringColumnTypeAdapter(context.processingEnv))
-            addColumnAdapter(ByteArrayColumnTypeAdapter(context.processingEnv))
-            PrimitiveBooleanToIntConverter.create(context.processingEnv).forEach(::addTypeConverter)
-            BoxedBooleanToBoxedIntConverter.create(context.processingEnv)
-                    .forEach(::addTypeConverter)
-            return TypeAdapterStore(context = context, columnTypeAdapters = adapters,
-                    typeConverters = converters)
-        }
-    }
-
-    val queryResultBinderProviders = listOf(
-            CursorQueryResultBinderProvider(context),
-            LiveDataQueryResultBinderProvider(context),
-            FlowableQueryResultBinderProvider(context),
-            GuavaListenableFutureQueryResultBinderProvider(context),
-            RxMaybeQueryResultBinderProvider(context),
-            RxSingleQueryResultBinderProvider(context),
-            DataSourceQueryResultBinderProvider(context),
-            DataSourceFactoryQueryResultBinderProvider(context),
-            InstantQueryResultBinderProvider(context)
-    )
-
-    // type mirrors that be converted into columns w/o an extra converter
-    private val knownColumnTypeMirrors by lazy {
-        columnTypeAdapters.map { it.out }
-    }
-
-    /**
-     * Searches 1 way to bind a value into a statement.
-     */
-    fun findStatementValueBinder(
-            input: TypeMirror,
-            affinity: SQLTypeAffinity?
-    ): StatementValueBinder? {
-        if (input.kind == TypeKind.ERROR) {
-            return null
-        }
-        val adapter = findDirectAdapterFor(input, affinity)
-        if (adapter != null) {
-            return adapter
-        }
-        val targetTypes = targetTypeMirrorsFor(affinity)
-        val binder = findTypeConverter(input, targetTypes) ?: return null
-        // columnAdapter should not be null but we are receiving errors on crash in `first()` so
-        // this safeguard allows us to dispatch the real problem to the user (e.g. why we couldn't
-        // find the right adapter)
-        val columnAdapter = getAllColumnAdapters(binder.to).firstOrNull() ?: return null
-        return CompositeAdapter(input, columnAdapter, binder, null)
-    }
-
-    /**
-     * Returns which entities targets the given affinity.
-     */
-    private fun targetTypeMirrorsFor(affinity: SQLTypeAffinity?): List<TypeMirror> {
-        val specifiedTargets = affinity?.getTypeMirrors(context.processingEnv)
-        return if (specifiedTargets == null || specifiedTargets.isEmpty()) {
-            knownColumnTypeMirrors
-        } else {
-            specifiedTargets
-        }
-    }
-
-    /**
-     * Searches 1 way to read it from cursor
-     */
-    fun findCursorValueReader(output: TypeMirror, affinity: SQLTypeAffinity?): CursorValueReader? {
-        if (output.kind == TypeKind.ERROR) {
-            return null
-        }
-        val adapter = findColumnTypeAdapter(output, affinity)
-        if (adapter != null) {
-            // two way is better
-            return adapter
-        }
-        // we could not find a two way version, search for anything
-        val targetTypes = targetTypeMirrorsFor(affinity)
-        val converter = findTypeConverter(targetTypes, output) ?: return null
-        return CompositeAdapter(output,
-                getAllColumnAdapters(converter.from).first(), null, converter)
-    }
-
-    /**
-     * Tries to reverse the converter going through the same nodes, if possible.
-     */
-    @VisibleForTesting
-    fun reverse(converter: TypeConverter): TypeConverter? {
-        return when (converter) {
-            is NoOpConverter -> converter
-            is CompositeTypeConverter -> {
-                val r1 = reverse(converter.conv1) ?: return null
-                val r2 = reverse(converter.conv2) ?: return null
-                CompositeTypeConverter(r2, r1)
-            }
-            else -> {
-                val types = context.processingEnv.typeUtils
-                typeConverters.firstOrNull {
-                    types.isSameType(it.from, converter.to) && types
-                            .isSameType(it.to, converter.from)
-                }
-            }
-        }
-    }
-
-    /**
-     * Finds a two way converter, if you need 1 way, use findStatementValueBinder or
-     * findCursorValueReader.
-     */
-    fun findColumnTypeAdapter(out: TypeMirror, affinity: SQLTypeAffinity?): ColumnTypeAdapter? {
-        if (out.kind == TypeKind.ERROR) {
-            return null
-        }
-        val adapter = findDirectAdapterFor(out, affinity)
-        if (adapter != null) {
-            return adapter
-        }
-        val targetTypes = targetTypeMirrorsFor(affinity)
-        val intoStatement = findTypeConverter(out, targetTypes) ?: return null
-        // ok found a converter, try the reverse now
-        val fromCursor = reverse(intoStatement) ?: findTypeConverter(intoStatement.to, out)
-                ?: return null
-        return CompositeAdapter(out, getAllColumnAdapters(intoStatement.to).first(), intoStatement,
-                fromCursor)
-    }
-
-    private fun findDirectAdapterFor(
-            out: TypeMirror, affinity: SQLTypeAffinity?): ColumnTypeAdapter? {
-        val adapter = getAllColumnAdapters(out).firstOrNull {
-            affinity == null || it.typeAffinity == affinity
-        }
-        return adapter
-    }
-
-    fun findTypeConverter(input: TypeMirror, output: TypeMirror): TypeConverter? {
-        return findTypeConverter(listOf(input), listOf(output))
-    }
-
-    fun findQueryResultBinder(typeMirror: TypeMirror, query: ParsedQuery): QueryResultBinder {
-        return if (typeMirror.kind == TypeKind.DECLARED) {
-            val declared = MoreTypes.asDeclared(typeMirror)
-            return queryResultBinderProviders.first {
-                it.matches(declared)
-            }.provide(declared, query)
-        } else {
-            InstantQueryResultBinder(findQueryResultAdapter(typeMirror, query))
-        }
-    }
-
-    fun findQueryResultAdapter(typeMirror: TypeMirror, query: ParsedQuery): QueryResultAdapter? {
-        if (typeMirror.kind == TypeKind.ERROR) {
-            return null
-        }
-        if (typeMirror.kind == TypeKind.DECLARED) {
-            val declared = MoreTypes.asDeclared(typeMirror)
-
-            if (declared.typeArguments.isEmpty()) {
-                val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
-                return SingleEntityQueryResultAdapter(rowAdapter)
-            } else if (
-                    context.processingEnv.typeUtils.erasure(typeMirror).typeName() ==
-                    GuavaBaseTypeNames.OPTIONAL) {
-                // Handle Guava Optional by unpacking its generic type argument and adapting that.
-                // The Optional adapter will reappend the Optional type.
-                val typeArg = declared.typeArguments.first()
-                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
-                return GuavaOptionalQueryResultAdapter(SingleEntityQueryResultAdapter(rowAdapter))
-            } else if (
-                    context.processingEnv.typeUtils.erasure(typeMirror).typeName() ==
-                    CommonTypeNames.OPTIONAL) {
-                // Handle java.util.Optional similarly.
-                val typeArg = declared.typeArguments.first()
-                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
-                return OptionalQueryResultAdapter(SingleEntityQueryResultAdapter(rowAdapter))
-            } else if (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)) {
-                val typeArg = declared.typeArguments.first()
-                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
-                return ListQueryResultAdapter(rowAdapter)
-            }
-            return null
-        } else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
-            val rowAdapter =
-                    findRowAdapter(typeMirror.componentType, query) ?: return null
-            return ArrayQueryResultAdapter(rowAdapter)
-        } else {
-            val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
-            return SingleEntityQueryResultAdapter(rowAdapter)
-        }
-    }
-
-    /**
-     * Find a converter from cursor to the given type mirror.
-     * If there is information about the query result, we try to use it to accept *any* POJO.
-     */
-    @VisibleForTesting
-    fun findRowAdapter(typeMirror: TypeMirror, query: ParsedQuery): RowAdapter? {
-        if (typeMirror.kind == TypeKind.ERROR) {
-            return null
-        }
-        if (typeMirror.kind == TypeKind.DECLARED) {
-            val declared = MoreTypes.asDeclared(typeMirror)
-            if (declared.typeArguments.isNotEmpty()) {
-                // TODO one day support this
-                return null
-            }
-            val resultInfo = query.resultInfo
-
-            val (rowAdapter, rowAdapterLogs) = if (resultInfo != null && query.errors.isEmpty()
-                    && resultInfo.error == null) {
-                // if result info is not null, first try a pojo row adapter
-                context.collectLogs { subContext ->
-                    val pojo = PojoProcessor(
-                            baseContext = subContext,
-                            element = MoreTypes.asTypeElement(typeMirror),
-                            bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
-                            parent = null
-                    ).process()
-                    PojoRowAdapter(
-                            context = subContext,
-                            info = resultInfo,
-                            pojo = pojo,
-                            out = typeMirror)
-                }
-            } else {
-                Pair(null, null)
-            }
-
-            if (rowAdapter == null && query.resultInfo == null) {
-                // we don't know what query returns. Check for entity.
-                val asElement = MoreTypes.asElement(typeMirror)
-                if (asElement.hasAnnotation(Entity::class)) {
-                    return EntityRowAdapter(EntityProcessor(context,
-                            MoreElements.asType(asElement)).process())
-                }
-            }
-
-            if (rowAdapter != null && rowAdapterLogs?.hasErrors() != true) {
-                rowAdapterLogs?.writeTo(context.processingEnv)
-                return rowAdapter
-            }
-
-            if ((resultInfo?.columns?.size ?: 1) == 1) {
-                val singleColumn = findCursorValueReader(typeMirror,
-                        resultInfo?.columns?.get(0)?.type)
-                if (singleColumn != null) {
-                    return SingleColumnRowAdapter(singleColumn)
-                }
-            }
-            // if we tried, return its errors
-            if (rowAdapter != null) {
-                rowAdapterLogs?.writeTo(context.processingEnv)
-                return rowAdapter
-            }
-            if (query.runtimeQueryPlaceholder) {
-                // just go w/ pojo and hope for the best. this happens for @RawQuery where we
-                // try to guess user's intention and hope that their query fits the result.
-                val pojo = PojoProcessor(
-                        baseContext = context,
-                        element = MoreTypes.asTypeElement(typeMirror),
-                        bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
-                        parent = null
-                ).process()
-                return PojoRowAdapter(
-                        context = context,
-                        info = null,
-                        pojo = pojo,
-                        out = typeMirror)
-            }
-            return null
-        } else {
-            val singleColumn = findCursorValueReader(typeMirror, null) ?: return null
-            return SingleColumnRowAdapter(singleColumn)
-        }
-    }
-
-    fun findQueryParameterAdapter(typeMirror: TypeMirror): QueryParameterAdapter? {
-        if (MoreTypes.isType(typeMirror)
-                && (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)
-                || MoreTypes.isTypeOf(java.util.Set::class.java, typeMirror))) {
-            val declared = MoreTypes.asDeclared(typeMirror)
-            val binder = findStatementValueBinder(declared.typeArguments.first(),
-                    null)
-            if (binder != null) {
-                return CollectionQueryParameterAdapter(binder)
-            } else {
-                // maybe user wants to convert this collection themselves. look for a match
-                val collectionBinder = findStatementValueBinder(typeMirror, null) ?: return null
-                return BasicQueryParameterAdapter(collectionBinder)
-            }
-        } else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
-            val component = typeMirror.componentType
-            val binder = findStatementValueBinder(component, null) ?: return null
-            return ArrayQueryParameterAdapter(binder)
-        } else {
-            val binder = findStatementValueBinder(typeMirror, null) ?: return null
-            return BasicQueryParameterAdapter(binder)
-        }
-    }
-
-    private fun findTypeConverter(input: TypeMirror, outputs: List<TypeMirror>): TypeConverter? {
-        return findTypeConverter(listOf(input), outputs)
-    }
-
-    private fun findTypeConverter(input: List<TypeMirror>, output: TypeMirror): TypeConverter? {
-        return findTypeConverter(input, listOf(output))
-    }
-
-    private fun findTypeConverter(
-            inputs: List<TypeMirror>, outputs: List<TypeMirror>): TypeConverter? {
-        if (inputs.isEmpty()) {
-            return null
-        }
-        val types = context.processingEnv.typeUtils
-        inputs.forEach { input ->
-            if (outputs.any { output -> types.isSameType(input, output) }) {
-                return NoOpConverter(input)
-            }
-        }
-
-        val excludes = arrayListOf<TypeMirror>()
-
-        val queue = LinkedList<TypeConverter>()
-        fun exactMatch(candidates: List<TypeConverter>): TypeConverter? {
-            return candidates.firstOrNull {
-                outputs.any { output -> types.isSameType(output, it.to) }
-            }
-        }
-        inputs.forEach { input ->
-            val candidates = getAllTypeConverters(input, excludes)
-            val match = exactMatch(candidates)
-            if (match != null) {
-                return match
-            }
-            candidates.forEach {
-                excludes.add(it.to)
-                queue.add(it)
-            }
-        }
-        excludes.addAll(inputs)
-        while (queue.isNotEmpty()) {
-            val prev = queue.pop()
-            val from = prev.to
-            val candidates = getAllTypeConverters(from, excludes)
-            val match = exactMatch(candidates)
-            if (match != null) {
-                return CompositeTypeConverter(prev, match)
-            }
-            candidates.forEach {
-                excludes.add(it.to)
-                queue.add(CompositeTypeConverter(prev, it))
-            }
-        }
-        return null
-    }
-
-    private fun getAllColumnAdapters(input: TypeMirror): List<ColumnTypeAdapter> {
-        return columnTypeAdapters.filter {
-            context.processingEnv.typeUtils.isSameType(input, it.out)
-        }
-    }
-
-    /**
-     * Returns all type converters that can receive input type and return into another type.
-     * The returned list is ordered by priority such that if we have an exact match, it is
-     * prioritized.
-     */
-    private fun getAllTypeConverters(input: TypeMirror, excludes: List<TypeMirror>):
-            List<TypeConverter> {
-        val types = context.processingEnv.typeUtils
-        // for input, check assignability because it defines whether we can use the method or not.
-        // for excludes, use exact match
-        return typeConverters.filter { converter ->
-            types.isAssignable(input, converter.from) &&
-                    !excludes.any { types.isSameType(it, converter.to) }
-        }.sortedByDescending {
-                    // if it is the same, prioritize
-                    if (types.isSameType(it.from, input)) {
-                        2
-                    } else {
-                        1
-                    }
-                }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/CursorQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/CursorQueryResultBinderProvider.kt
deleted file mode 100644
index e160672..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/CursorQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.binderprovider
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.solver.QueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.CursorQueryResultBinder
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import com.squareup.javapoet.TypeName
-import javax.lang.model.type.DeclaredType
-
-class CursorQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
-    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
-        return CursorQueryResultBinder()
-    }
-
-    override fun matches(declared: DeclaredType): Boolean =
-        declared.typeArguments.size == 0 && TypeName.get(declared) == AndroidTypeNames.CURSOR
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt
deleted file mode 100644
index c4fb238..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.binderprovider
-
-import android.arch.persistence.room.ext.PagingTypeNames
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.QueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.ListQueryResultAdapter
-import android.arch.persistence.room.solver.query.result.DataSourceFactoryQueryResultBinder
-import android.arch.persistence.room.solver.query.result.PositionalDataSourceQueryResultBinder
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeMirror
-
-class DataSourceFactoryQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
-    private val dataSourceFactoryTypeMirror: TypeMirror? by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY.toString())?.asType()
-    }
-
-    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
-        if (query.tables.isEmpty()) {
-            context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-        }
-        val typeArg = declared.typeArguments[1]
-        val adapter = context.typeAdapterStore.findRowAdapter(typeArg, query)?.let {
-            ListQueryResultAdapter(it)
-        }
-
-        val tableNames = ((adapter?.accessedTableNames() ?: emptyList())
-                + query.tables.map { it.name }).toSet()
-        val countedBinder = PositionalDataSourceQueryResultBinder(adapter, tableNames)
-        return DataSourceFactoryQueryResultBinder(countedBinder)
-    }
-
-    override fun matches(declared: DeclaredType): Boolean =
-            declared.typeArguments.size == 2 && isLivePagedList(declared)
-
-    private fun isLivePagedList(declared: DeclaredType): Boolean {
-        if (dataSourceFactoryTypeMirror == null) {
-            return false
-        }
-        val erasure = context.processingEnv.typeUtils.erasure(declared)
-        // we don't want to return paged list unless explicitly requested
-        return context.processingEnv.typeUtils.isAssignable(dataSourceFactoryTypeMirror, erasure)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt
deleted file mode 100644
index 56eac6b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.binderprovider
-
-import android.arch.persistence.room.ext.PagingTypeNames
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.QueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.PositionalDataSourceQueryResultBinder
-import android.arch.persistence.room.solver.query.result.ListQueryResultAdapter
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeMirror
-
-class DataSourceQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
-    private val dataSourceTypeMirror: TypeMirror? by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(PagingTypeNames.DATA_SOURCE.toString())?.asType()
-    }
-
-    private val positionalDataSourceTypeMirror: TypeMirror? by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(PagingTypeNames.POSITIONAL_DATA_SOURCE.toString())?.asType()
-    }
-
-    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
-        if (query.tables.isEmpty()) {
-            context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-        }
-        val typeArg = declared.typeArguments.last()
-        val listAdapter = context.typeAdapterStore.findRowAdapter(typeArg, query)?.let {
-            ListQueryResultAdapter(it)
-        }
-        val tableNames = ((listAdapter?.accessedTableNames() ?: emptyList())
-                + query.tables.map { it.name }).toSet()
-        return PositionalDataSourceQueryResultBinder(listAdapter, tableNames)
-    }
-
-    override fun matches(declared: DeclaredType): Boolean {
-        if (dataSourceTypeMirror == null || positionalDataSourceTypeMirror == null) {
-            return false
-        }
-        if (declared.typeArguments.isEmpty()) {
-            return false
-        }
-        val erasure = context.processingEnv.typeUtils.erasure(declared)
-        val isDataSource = context.processingEnv.typeUtils
-                .isAssignable(erasure, dataSourceTypeMirror)
-        if (!isDataSource) {
-            return false
-        }
-        val isPositional = context.processingEnv.typeUtils
-                .isAssignable(erasure, positionalDataSourceTypeMirror)
-        if (!isPositional) {
-            context.logger.e(ProcessorErrors.PAGING_SPECIFY_DATA_SOURCE_TYPE)
-        }
-        return true
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/FlowableQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/FlowableQueryResultBinderProvider.kt
deleted file mode 100644
index 6a2c901..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/FlowableQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.binderprovider
-
-import android.arch.persistence.room.ext.RoomRxJava2TypeNames
-import android.arch.persistence.room.ext.RxJava2TypeNames
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.ObservableQueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.FlowableQueryResultBinder
-import android.arch.persistence.room.solver.query.result.QueryResultAdapter
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeMirror
-
-class FlowableQueryResultBinderProvider(context: Context) :
-        ObservableQueryResultBinderProvider(context) {
-    private val flowableTypeMirror: TypeMirror? by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(RxJava2TypeNames.FLOWABLE.toString())?.asType()
-    }
-
-    private val hasRxJava2Artifact by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(RoomRxJava2TypeNames.RX_ROOM.toString()) != null
-    }
-
-    override fun extractTypeArg(declared: DeclaredType): TypeMirror = declared.typeArguments.first()
-
-    override fun create(typeArg: TypeMirror, resultAdapter: QueryResultAdapter?,
-                        tableNames: Set<String>): QueryResultBinder {
-        return FlowableQueryResultBinder(
-                typeArg = typeArg,
-                queryTableNames = tableNames,
-                adapter = resultAdapter)
-    }
-
-    override fun matches(declared: DeclaredType): Boolean =
-            declared.typeArguments.size == 1 && isRxJava2Publisher(declared)
-
-    private fun isRxJava2Publisher(declared: DeclaredType): Boolean {
-        if (flowableTypeMirror == null) {
-            return false
-        }
-        val erasure = context.processingEnv.typeUtils.erasure(declared)
-        val match = context.processingEnv.typeUtils.isAssignable(flowableTypeMirror, erasure)
-        if (match && !hasRxJava2Artifact) {
-            context.logger.e(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
-        }
-        return match
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/GuavaListenableFutureQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/GuavaListenableFutureQueryResultBinderProvider.kt
deleted file mode 100644
index 7876a63..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/GuavaListenableFutureQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2018 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.solver.binderprovider
-
-import android.arch.persistence.room.ext.GuavaUtilConcurrentTypeNames
-import android.arch.persistence.room.ext.RoomGuavaTypeNames
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.QueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import android.arch.persistence.room.solver.query.result.GuavaListenableFutureQueryResultBinder
-import javax.lang.model.type.DeclaredType
-
-class GuavaListenableFutureQueryResultBinderProvider(val context: Context)
-    : QueryResultBinderProvider {
-
-    private val hasGuavaRoom by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(RoomGuavaTypeNames.GUAVA_ROOM.toString()) != null
-    }
-
-    /**
-     * Returns the {@link GuavaListenableFutureQueryResultBinder} instance for the input type, if
-     * possible.
-     *
-     * <p>Emits a compiler error if the Guava Room extension library is not linked.
-     */
-    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
-        if (!hasGuavaRoom) {
-            context.logger.e(ProcessorErrors.MISSING_ROOM_GUAVA_ARTIFACT)
-        }
-
-        // Use the type T inside ListenableFuture<T> as the type to adapt and to pass into
-        // the binder.
-        val adapter = context.typeAdapterStore.findQueryResultAdapter(
-                declared.typeArguments.first(), query)
-        return GuavaListenableFutureQueryResultBinder(
-                declared.typeArguments.first(), adapter)
-    }
-
-    /**
-     * Returns true iff the input {@code declared} type is ListenableFuture<T>.
-     */
-    override fun matches(declared: DeclaredType): Boolean =
-        declared.typeArguments.size == 1 &&
-                context.processingEnv.typeUtils.erasure(declared).typeName() ==
-                        GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/InstantQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/InstantQueryResultBinderProvider.kt
deleted file mode 100644
index c7d1e2d..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/InstantQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.binderprovider
-
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.solver.QueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.InstantQueryResultBinder
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import javax.lang.model.type.DeclaredType
-
-class InstantQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
-    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
-        return InstantQueryResultBinder(
-                context.typeAdapterStore.findQueryResultAdapter(declared, query))
-    }
-
-    override fun matches(declared: DeclaredType): Boolean = true
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/LiveDataQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/LiveDataQueryResultBinderProvider.kt
deleted file mode 100644
index b62eabd..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/LiveDataQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.binderprovider
-
-import android.arch.persistence.room.ext.LifecyclesTypeNames
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.solver.ObservableQueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.LiveDataQueryResultBinder
-import android.arch.persistence.room.solver.query.result.QueryResultAdapter
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeMirror
-
-class LiveDataQueryResultBinderProvider(context: Context)
-    : ObservableQueryResultBinderProvider(context) {
-    private val liveDataTypeMirror: TypeMirror? by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(LifecyclesTypeNames.LIVE_DATA.toString())?.asType()
-    }
-
-    override fun extractTypeArg(declared: DeclaredType): TypeMirror = declared.typeArguments.first()
-
-    override fun create(typeArg: TypeMirror, resultAdapter: QueryResultAdapter?,
-                        tableNames: Set<String>): QueryResultBinder {
-        return LiveDataQueryResultBinder(
-                typeArg = typeArg,
-                tableNames = tableNames,
-                adapter = resultAdapter)
-    }
-
-    override fun matches(declared: DeclaredType): Boolean =
-            declared.typeArguments.size == 1 && isLiveData(declared)
-
-    private fun isLiveData(declared: DeclaredType): Boolean {
-        if (liveDataTypeMirror == null) {
-            return false
-        }
-        val erasure = context.processingEnv.typeUtils.erasure(declared)
-        return context.processingEnv.typeUtils.isAssignable(liveDataTypeMirror, erasure)
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
deleted file mode 100644
index 11b824f..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.binderprovider
-
-import android.arch.persistence.room.ext.RoomRxJava2TypeNames
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.QueryResultBinderProvider
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import android.arch.persistence.room.solver.query.result.RxCallableQueryResultBinder
-import javax.lang.model.type.DeclaredType
-
-sealed class RxCallableQueryResultBinderProvider(val context: Context,
-                                                 val rxType: RxCallableQueryResultBinder.RxType)
-    : QueryResultBinderProvider {
-    private val hasRxJava2Artifact by lazy {
-        context.processingEnv.elementUtils
-                .getTypeElement(RoomRxJava2TypeNames.RX_ROOM.toString()) != null
-    }
-
-    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
-        val typeArg = declared.typeArguments.first()
-        val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query)
-        return RxCallableQueryResultBinder(rxType, typeArg, adapter)
-    }
-
-    override fun matches(declared: DeclaredType): Boolean =
-            declared.typeArguments.size == 1 && matchesRxType(declared)
-
-    private fun matchesRxType(declared: DeclaredType): Boolean {
-        val erasure = context.processingEnv.typeUtils.erasure(declared)
-        val match = erasure.typeName() == rxType.className
-        if (match && !hasRxJava2Artifact) {
-            context.logger.e(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
-        }
-        return match
-    }
-}
-
-class RxSingleQueryResultBinderProvider(context: Context)
-    : RxCallableQueryResultBinderProvider(context, RxCallableQueryResultBinder.RxType.SINGLE)
-
-class RxMaybeQueryResultBinderProvider(context: Context)
-    : RxCallableQueryResultBinderProvider(context, RxCallableQueryResultBinder.RxType.MAYBE)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/ArrayQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
deleted file mode 100644
index 12756d8..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.parameter
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.solver.types.StatementValueBinder
-import com.squareup.javapoet.TypeName
-
-/**
- * Binds ARRAY(T) (e.g. int[]) into String[] args of a query.
- */
-class ArrayQueryParameterAdapter(val bindAdapter: StatementValueBinder)
-            : QueryParameterAdapter(true) {
-    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
-                            scope: CodeGenScope) {
-        scope.builder().apply {
-            val itrVar = scope.getTmpVar("_item")
-            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
-                    inputVarName).apply {
-                        bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
-                        addStatement("$L ++", startIndexVarName)
-            }
-            endControlFlow()
-        }
-    }
-
-    override fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("final $T $L = $L.length", TypeName.INT, outputVarName, inputVarName)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/BasicQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/BasicQueryParameterAdapter.kt
deleted file mode 100644
index 41ab2cf..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/BasicQueryParameterAdapter.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.parameter
-
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.solver.types.StatementValueBinder
-
-/**
- * Knows how to convert a query parameter into arguments
- */
-class BasicQueryParameterAdapter(val bindAdapter: StatementValueBinder)
-            : QueryParameterAdapter(false) {
-    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
-                            scope: CodeGenScope) {
-        scope.builder().apply {
-            bindAdapter.bindToStmt(stmtVarName, startIndexVarName, inputVarName, scope)
-        }
-    }
-
-    override fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
-        throw UnsupportedOperationException("should not call getArgCount on basic adapters." +
-                "It is always one.")
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/CollectionQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
deleted file mode 100644
index 00c350a..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.parameter
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.solver.types.StatementValueBinder
-import com.squareup.javapoet.TypeName
-
-/**
- * Binds Collection<T> (e.g. List<T>) into String[] query args.
- */
-class CollectionQueryParameterAdapter(val bindAdapter: StatementValueBinder)
-            : QueryParameterAdapter(true) {
-    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
-                            scope: CodeGenScope) {
-        scope.builder().apply {
-            val itrVar = scope.getTmpVar("_item")
-            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
-                    inputVarName).apply {
-                        bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
-                        addStatement("$L ++", startIndexVarName)
-                    }
-            endControlFlow()
-        }
-    }
-
-    override fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("final $T $L = $L.size()", TypeName.INT, outputVarName, inputVarName)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/QueryParameterAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/QueryParameterAdapter.kt
deleted file mode 100644
index a42bfee..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/parameter/QueryParameterAdapter.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.parameter
-
-import android.arch.persistence.room.solver.CodeGenScope
-
-/**
- * Knows how to convert a query parameter into query arguments.
- */
-abstract class QueryParameterAdapter(val isMultiple: Boolean) {
-    /**
-     * Must bind the value into the statement at the given index.
-     */
-    abstract fun bindToStmt(
-            inputVarName: String,
-            stmtVarName: String,
-            startIndexVarName: String,
-            scope: CodeGenScope)
-
-    /**
-     * Should declare and set the given value with the count
-     */
-    abstract fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope)
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ArrayQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ArrayQueryResultAdapter.kt
deleted file mode 100644
index e8330bc..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ArrayQueryResultAdapter.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
-
-class ArrayQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
-    val type = rowAdapter.out
-    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            rowAdapter?.onCursorReady(cursorVarName, scope)
-
-            val arrayType = ArrayTypeName.of(type.typeName())
-            addStatement("final $T $L = new $T[$L.getCount()]",
-                    arrayType, outVarName, type.typeName(), cursorVarName)
-            val tmpVarName = scope.getTmpVar("_item")
-            val indexVar = scope.getTmpVar("_index")
-            addStatement("$T $L = 0", TypeName.INT, indexVar)
-            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
-                addStatement("final $T $L", type.typeName(), tmpVarName)
-                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
-                addStatement("$L[$L] = $L", outVarName, indexVar, tmpVarName)
-                addStatement("$L ++", indexVar)
-            }
-            endControlFlow()
-            rowAdapter?.onCursorFinished()?.invoke(scope)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt
deleted file mode 100644
index 883180c..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/BaseObservableQueryResultBinder.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.writer.DaoWriter
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import javax.lang.model.element.Modifier
-
-/**
- * Base class for query result binders that observe the database. It includes common functionality
- * like creating a finalizer to release the query or creating the actual adapter call code.
- */
-abstract class BaseObservableQueryResultBinder(adapter: QueryResultAdapter?)
-    : QueryResultBinder(adapter) {
-
-    protected fun createFinalizeMethod(roomSQLiteQueryVar: String): MethodSpec {
-        return MethodSpec.methodBuilder("finalize").apply {
-            addModifiers(Modifier.PROTECTED)
-            addAnnotation(Override::class.java)
-            addStatement("$L.release()", roomSQLiteQueryVar)
-        }.build()
-    }
-
-    protected fun createRunQueryAndReturnStatements(builder: MethodSpec.Builder,
-                                                    roomSQLiteQueryVar: String,
-                                                    dbField: FieldSpec,
-                                                    inTransaction: Boolean,
-                                                    scope: CodeGenScope) {
-        val transactionWrapper = if (inTransaction) {
-            builder.transactionWrapper(dbField)
-        } else {
-            null
-        }
-        val outVar = scope.getTmpVar("_result")
-        val cursorVar = scope.getTmpVar("_cursor")
-        transactionWrapper?.beginTransactionWithControlFlow()
-        builder.apply {
-            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
-                    DaoWriter.dbField, roomSQLiteQueryVar)
-            beginControlFlow("try").apply {
-                val adapterScope = scope.fork()
-                adapter?.convert(outVar, cursorVar, adapterScope)
-                addCode(adapterScope.builder().build())
-                transactionWrapper?.commitTransaction()
-                addStatement("return $L", outVar)
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$L.close()", cursorVar)
-            }
-            endControlFlow()
-        }
-        transactionWrapper?.endTransactionWithControlFlow()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt
deleted file mode 100644
index 7aa24cc..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/CursorQueryResultBinder.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.FieldSpec
-
-class CursorQueryResultBinder : QueryResultBinder(NO_OP_RESULT_ADAPTER) {
-    override fun convertAndReturn(roomSQLiteQueryVar: String,
-                                  canReleaseQuery: Boolean,
-                                  dbField: FieldSpec,
-                                  inTransaction: Boolean,
-                                  scope: CodeGenScope) {
-        val builder = scope.builder()
-        val transactionWrapper = if (inTransaction) {
-            builder.transactionWrapper(dbField)
-        } else {
-            null
-        }
-        transactionWrapper?.beginTransactionWithControlFlow()
-        val resultName = scope.getTmpVar("_tmpResult")
-        builder.addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, resultName,
-                dbField, roomSQLiteQueryVar)
-        transactionWrapper?.commitTransaction()
-        builder.addStatement("return $L", resultName)
-        transactionWrapper?.endTransactionWithControlFlow()
-    }
-
-    companion object {
-        private val NO_OP_RESULT_ADAPTER = object : QueryResultAdapter(null) {
-            override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
deleted file mode 100644
index 93b2d7b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.PagingTypeNames
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier
-
-class DataSourceFactoryQueryResultBinder(
-        val positionalDataSourceQueryResultBinder: PositionalDataSourceQueryResultBinder)
-    : QueryResultBinder(positionalDataSourceQueryResultBinder.listAdapter) {
-    @Suppress("HasPlatformType")
-    val typeName = positionalDataSourceQueryResultBinder.itemTypeName
-    override fun convertAndReturn(
-            roomSQLiteQueryVar: String,
-            canReleaseQuery: Boolean,
-            dbField: FieldSpec,
-            inTransaction: Boolean,
-            scope: CodeGenScope
-    ) {
-        scope.builder().apply {
-            val pagedListProvider = TypeSpec
-                    .anonymousClassBuilder("").apply {
-                superclass(ParameterizedTypeName.get(PagingTypeNames.DATA_SOURCE_FACTORY,
-                        Integer::class.typeName(), typeName))
-                addMethod(createCreateMethod(
-                        roomSQLiteQueryVar = roomSQLiteQueryVar,
-                        dbField = dbField,
-                        inTransaction = inTransaction,
-                        scope = scope))
-            }.build()
-            addStatement("return $L", pagedListProvider)
-        }
-    }
-
-    private fun createCreateMethod(
-            roomSQLiteQueryVar: String,
-            dbField: FieldSpec,
-            inTransaction: Boolean,
-            scope: CodeGenScope
-    ): MethodSpec = MethodSpec.methodBuilder("create").apply {
-        addAnnotation(Override::class.java)
-        addModifiers(Modifier.PUBLIC)
-        returns(positionalDataSourceQueryResultBinder.typeName)
-        val countedBinderScope = scope.fork()
-        positionalDataSourceQueryResultBinder.convertAndReturn(
-                roomSQLiteQueryVar = roomSQLiteQueryVar,
-                canReleaseQuery = true,
-                dbField = dbField,
-                inTransaction = inTransaction,
-                scope = countedBinderScope)
-        addCode(countedBinderScope.builder().build())
-    }.build()
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/EntityRowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/EntityRowAdapter.kt
deleted file mode 100644
index c97f69e..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/EntityRowAdapter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.writer.EntityCursorConverterWriter
-import com.squareup.javapoet.MethodSpec
-
-class EntityRowAdapter(val entity: Entity) : RowAdapter(entity.type) {
-    lateinit var methodSpec: MethodSpec
-    override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
-        methodSpec = scope.writer.getOrCreateMethod(EntityCursorConverterWriter(entity))
-    }
-
-    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("$L = $N($L)", outVarName, methodSpec, cursorVarName)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt
deleted file mode 100644
index 0fb21b1..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/FlowableQueryResultBinder.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomRxJava2TypeNames
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.arrayTypeName
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.writer.DaoWriter
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier
-import javax.lang.model.type.TypeMirror
-
-/**
- * Binds the result as an RxJava2 Flowable.
- */
-class FlowableQueryResultBinder(val typeArg: TypeMirror, val queryTableNames: Set<String>,
-                                adapter: QueryResultAdapter?)
-    : BaseObservableQueryResultBinder(adapter) {
-    override fun convertAndReturn(roomSQLiteQueryVar: String,
-                                  canReleaseQuery: Boolean,
-                                  dbField: FieldSpec,
-                                  inTransaction: Boolean,
-                                  scope: CodeGenScope) {
-        val callableImpl = TypeSpec.anonymousClassBuilder("").apply {
-            val typeName = typeArg.typeName()
-            superclass(ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
-                    typeName))
-            addMethod(MethodSpec.methodBuilder("call").apply {
-                returns(typeName)
-                addException(Exception::class.typeName())
-                addModifiers(Modifier.PUBLIC)
-                addAnnotation(Override::class.java)
-                createRunQueryAndReturnStatements(builder = this,
-                        roomSQLiteQueryVar = roomSQLiteQueryVar,
-                        inTransaction = inTransaction,
-                        dbField = dbField,
-                        scope = scope)
-            }.build())
-            if (canReleaseQuery) {
-                addMethod(createFinalizeMethod(roomSQLiteQueryVar))
-            }
-        }.build()
-        scope.builder().apply {
-            val tableNamesList = queryTableNames.joinToString(",") { "\"$it\"" }
-            addStatement("return $T.createFlowable($N, new $T{$L}, $L)",
-                    RoomRxJava2TypeNames.RX_ROOM, DaoWriter.dbField,
-                    String::class.arrayTypeName(), tableNamesList, callableImpl)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
deleted file mode 100644
index de57d78..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2018 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.solver.query.result
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomGuavaTypeNames
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.writer.DaoWriter
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier
-import javax.lang.model.type.TypeMirror
-
-/**
- * A ResultBinder that emits a ListenableFuture<T> where T is the input {@code typeArg}.
- *
- * <p>The Future runs on the background thread Executor.
- */
-class GuavaListenableFutureQueryResultBinder(
-        val typeArg: TypeMirror,
-        adapter: QueryResultAdapter?)
-    : BaseObservableQueryResultBinder(adapter) {
-
-    override fun convertAndReturn(
-            roomSQLiteQueryVar: String,
-            canReleaseQuery: Boolean,
-            dbField: FieldSpec,
-            inTransaction: Boolean,
-            scope: CodeGenScope) {
-        // Callable<T>
-        val callableImpl = createCallableOfT(
-                roomSQLiteQueryVar,
-                dbField,
-                inTransaction,
-                scope)
-
-        scope.builder().apply {
-            addStatement(
-                    "return $T.createListenableFuture($L, $L, $L)",
-                    RoomGuavaTypeNames.GUAVA_ROOM,
-                    callableImpl,
-                    roomSQLiteQueryVar,
-                    canReleaseQuery)
-        }
-    }
-
-    /**
-     * Returns an anonymous subclass of Callable<T> that executes the database transaction and
-     * constitutes the result T.
-     *
-     * <p>Note that this method does not release the query object.
-     */
-    private fun createCallableOfT(
-            roomSQLiteQueryVar: String,
-            dbField: FieldSpec,
-            inTransaction: Boolean,
-            scope: CodeGenScope): TypeSpec {
-        return TypeSpec.anonymousClassBuilder("").apply {
-            superclass(
-                    ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
-                            typeArg.typeName()))
-            addMethod(
-                    MethodSpec.methodBuilder("call").apply {
-                        // public T call() throws Exception {}
-                        returns(typeArg.typeName())
-                        addAnnotation(Override::class.typeName())
-                        addModifiers(Modifier.PUBLIC)
-                        addException(Exception::class.typeName())
-
-                        // Body.
-                        val transactionWrapper = if (inTransaction) {
-                            transactionWrapper(dbField)
-                        } else {
-                            null
-                        }
-                        transactionWrapper?.beginTransactionWithControlFlow()
-                        apply {
-                            val outVar = scope.getTmpVar("_result")
-                            val cursorVar = scope.getTmpVar("_cursor")
-                            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR,
-                                    cursorVar,
-                                    DaoWriter.dbField, roomSQLiteQueryVar)
-                            beginControlFlow("try").apply {
-                                val adapterScope = scope.fork()
-                                adapter?.convert(outVar, cursorVar, adapterScope)
-                                addCode(adapterScope.builder().build())
-                                transactionWrapper?.commitTransaction()
-                                addStatement("return $L", outVar)
-                            }
-                            nextControlFlow("finally").apply {
-                                addStatement("$L.close()", cursorVar)
-                            }
-                            endControlFlow()
-                        }
-                        transactionWrapper?.endTransactionWithControlFlow()
-                    }.build())
-        }.build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt
deleted file mode 100644
index 33fb37b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2018 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.solver.query.result
-
-import android.arch.persistence.room.ext.GuavaBaseTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.ParameterizedTypeName
-
-/**
- * Wraps a row adapter when there is only 1 item in the result, and the result's outer type is
- * {@link com.google.common.base.Optional}.
- */
-class GuavaOptionalQueryResultAdapter(private val resultAdapter: SingleEntityQueryResultAdapter)
-    : QueryResultAdapter(resultAdapter.rowAdapter) {
-    val type = resultAdapter.rowAdapter?.out
-    override fun convert(
-            outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            val valueVarName = scope.getTmpVar("_value")
-            resultAdapter?.convert(valueVarName, cursorVarName, scope)
-            addStatement(
-                    "final $T $L = $T.fromNullable($L)",
-                    ParameterizedTypeName.get(GuavaBaseTypeNames.OPTIONAL, type?.typeName()),
-                    outVarName,
-                    GuavaBaseTypeNames.OPTIONAL,
-                    valueVarName)
-        }
-    }
-}
-
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt
deleted file mode 100644
index aa64b1b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/InstantQueryResultBinder.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.writer.DaoWriter
-import com.squareup.javapoet.FieldSpec
-
-/**
- * Instantly runs and returns the query.
- */
-class InstantQueryResultBinder(adapter: QueryResultAdapter?) : QueryResultBinder(adapter) {
-    override fun convertAndReturn(roomSQLiteQueryVar: String,
-                                  canReleaseQuery: Boolean,
-                                  dbField: FieldSpec,
-                                  inTransaction: Boolean,
-                                  scope: CodeGenScope) {
-        val transactionWrapper = if (inTransaction) {
-            scope.builder().transactionWrapper(dbField)
-        } else {
-            null
-        }
-        transactionWrapper?.beginTransactionWithControlFlow()
-        scope.builder().apply {
-            val outVar = scope.getTmpVar("_result")
-            val cursorVar = scope.getTmpVar("_cursor")
-            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
-                    DaoWriter.dbField, roomSQLiteQueryVar)
-            beginControlFlow("try").apply {
-                adapter?.convert(outVar, cursorVar, scope)
-                transactionWrapper?.commitTransaction()
-                addStatement("return $L", outVar)
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$L.close()", cursorVar)
-                if (canReleaseQuery) {
-                    addStatement("$L.release()", roomSQLiteQueryVar)
-                }
-            }
-            endControlFlow()
-        }
-        transactionWrapper?.endTransactionWithControlFlow()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ListQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ListQueryResultAdapter.kt
deleted file mode 100644
index b70ec5a..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/ListQueryResultAdapter.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import java.util.ArrayList
-
-class ListQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
-    val type = rowAdapter.out
-    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            rowAdapter?.onCursorReady(cursorVarName, scope)
-            val collectionType = ParameterizedTypeName
-                    .get(ClassName.get(List::class.java), type.typeName())
-            val arrayListType = ParameterizedTypeName
-                    .get(ClassName.get(ArrayList::class.java), type.typeName())
-            addStatement("final $T $L = new $T($L.getCount())",
-                    collectionType, outVarName, arrayListType, cursorVarName)
-            val tmpVarName = scope.getTmpVar("_item")
-            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
-                addStatement("final $T $L", type.typeName(), tmpVarName)
-                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
-                addStatement("$L.add($L)", outVarName, tmpVarName)
-            }
-            endControlFlow()
-            rowAdapter?.onCursorFinished()?.invoke(scope)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt
deleted file mode 100644
index 416b8b8..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.LifecyclesTypeNames
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.RoomTypeNames.INVALIDATION_OBSERVER
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import android.support.annotation.NonNull
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier
-import javax.lang.model.type.TypeMirror
-
-/**
- * Converts the query into a LiveData and returns it. No query is run until necessary.
- */
-class LiveDataQueryResultBinder(val typeArg: TypeMirror, val tableNames: Set<String>,
-                                adapter: QueryResultAdapter?)
-    : BaseObservableQueryResultBinder(adapter) {
-    @Suppress("JoinDeclarationAndAssignment")
-    override fun convertAndReturn(
-            roomSQLiteQueryVar: String,
-            canReleaseQuery: Boolean,
-            dbField: FieldSpec,
-            inTransaction: Boolean,
-            scope: CodeGenScope
-    ) {
-        val typeName = typeArg.typeName()
-
-        val liveDataImpl = TypeSpec.anonymousClassBuilder("").apply {
-            superclass(ParameterizedTypeName.get(LifecyclesTypeNames.COMPUTABLE_LIVE_DATA,
-                    typeName))
-            val observerField = FieldSpec.builder(RoomTypeNames.INVALIDATION_OBSERVER,
-                    scope.getTmpVar("_observer"), Modifier.PRIVATE).build()
-            addField(observerField)
-            addMethod(createComputeMethod(
-                    observerField = observerField,
-                    typeName = typeName,
-                    roomSQLiteQueryVar = roomSQLiteQueryVar,
-                    dbField = dbField,
-                    inTransaction = inTransaction,
-                    scope = scope
-            ))
-            if (canReleaseQuery) {
-                addMethod(createFinalizeMethod(roomSQLiteQueryVar))
-            }
-        }.build()
-        scope.builder().apply {
-            addStatement("return $L.getLiveData()", liveDataImpl)
-        }
-    }
-
-    private fun createComputeMethod(
-            roomSQLiteQueryVar: String,
-            typeName: TypeName,
-            observerField: FieldSpec,
-            dbField: FieldSpec,
-            inTransaction: Boolean,
-            scope: CodeGenScope
-    ): MethodSpec {
-        return MethodSpec.methodBuilder("compute").apply {
-            addAnnotation(Override::class.java)
-            addModifiers(Modifier.PROTECTED)
-            returns(typeName)
-
-            beginControlFlow("if ($N == null)", observerField).apply {
-                addStatement("$N = $L", observerField, createAnonymousObserver())
-                addStatement("$N.getInvalidationTracker().addWeakObserver($N)",
-                        dbField, observerField)
-            }
-            endControlFlow()
-
-            createRunQueryAndReturnStatements(builder = this,
-                    roomSQLiteQueryVar = roomSQLiteQueryVar,
-                    dbField = dbField,
-                    inTransaction = inTransaction,
-                    scope = scope)
-        }.build()
-    }
-
-    private fun createAnonymousObserver(): TypeSpec {
-        val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
-        return TypeSpec.anonymousClassBuilder(tableNamesList).apply {
-            superclass(INVALIDATION_OBSERVER)
-            addMethod(MethodSpec.methodBuilder("onInvalidated").apply {
-                returns(TypeName.VOID)
-                addAnnotation(Override::class.java)
-                addParameter(ParameterSpec.builder(
-                        ParameterizedTypeName.get(Set::class.java, String::class.java), "tables")
-                        .addAnnotation(NonNull::class.java)
-                        .build())
-                addModifiers(Modifier.PUBLIC)
-                addStatement("invalidate()")
-            }.build())
-        }.build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/OptionalQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/OptionalQueryResultAdapter.kt
deleted file mode 100644
index a092eee..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/OptionalQueryResultAdapter.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2018 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.solver.query.result
-
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.ParameterizedTypeName
-
-/**
- * Wraps a row adapter when there is only 1 item in the result, and the result's outer type is
- * {@link java.util.Optional}.
- *
- * <p>n.b. this will only be useful if the project uses Java 8.
- */
-class OptionalQueryResultAdapter(private val resultAdapter: SingleEntityQueryResultAdapter)
-    : QueryResultAdapter(resultAdapter.rowAdapter) {
-    val type = resultAdapter.rowAdapter?.out
-    override fun convert(
-            outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            val valueVarName = scope.getTmpVar("_value")
-            resultAdapter?.convert(valueVarName, cursorVarName, scope)
-            addStatement(
-                    "final $T $L = $T.ofNullable($L)",
-                    ParameterizedTypeName.get(CommonTypeNames.OPTIONAL, type?.typeName()),
-                    outVarName,
-                    CommonTypeNames.OPTIONAL,
-                    valueVarName)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PojoRowAdapter.kt
deleted file mode 100644
index ee6e88b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PojoRowAdapter.kt
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.verifier.QueryResultInfo
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.FieldWithIndex
-import android.arch.persistence.room.vo.Pojo
-import android.arch.persistence.room.vo.RelationCollector
-import android.arch.persistence.room.vo.Warning
-import android.arch.persistence.room.writer.FieldReadWriteWriter
-import com.squareup.javapoet.TypeName
-import stripNonJava
-import javax.lang.model.type.TypeMirror
-
-/**
- * Creates the entity from the given info.
- * <p>
- * The info comes from the query processor so we know about the order of columns in the result etc.
- */
-class PojoRowAdapter(
-        context: Context, private val info: QueryResultInfo?,
-        val pojo: Pojo, out: TypeMirror) : RowAdapter(out) {
-    val mapping: Mapping
-    val relationCollectors: List<RelationCollector>
-
-    init {
-        // toMutableList documentation is not clear if it copies so lets be safe.
-        val remainingFields = pojo.fields.mapTo(mutableListOf(), { it })
-        val unusedColumns = arrayListOf<String>()
-        val matchedFields: List<Field>
-        if (info != null) {
-            matchedFields = info.columns.map { column ->
-                // first check remaining, otherwise check any. maybe developer wants to map the same
-                // column into 2 fields. (if they want to post process etc)
-                val field = remainingFields.firstOrNull { it.columnName == column.name } ?:
-                        pojo.fields.firstOrNull { it.columnName == column.name }
-                if (field == null) {
-                    unusedColumns.add(column.name)
-                    null
-                } else {
-                    remainingFields.remove(field)
-                    field
-                }
-            }.filterNotNull()
-            if (unusedColumns.isNotEmpty() || remainingFields.isNotEmpty()) {
-                val warningMsg = ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = pojo.typeName,
-                        unusedColumns = unusedColumns,
-                        allColumns = info.columns.map { it.name },
-                        unusedFields = remainingFields,
-                        allFields = pojo.fields
-                )
-                context.logger.w(Warning.CURSOR_MISMATCH, null, warningMsg)
-            }
-            val nonNulls = remainingFields.filter { it.nonNull }
-            if (nonNulls.isNotEmpty()) {
-                context.logger.e(ProcessorErrors.pojoMissingNonNull(
-                        pojoTypeName = pojo.typeName,
-                        missingPojoFields = nonNulls.map { it.name },
-                        allQueryColumns = info.columns.map { it.name }))
-            }
-            if (matchedFields.isEmpty()) {
-                context.logger.e(ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
-            }
-        } else {
-            matchedFields = remainingFields.map { it }
-            remainingFields.clear()
-        }
-        relationCollectors = RelationCollector.createCollectors(context, pojo.relations)
-
-        mapping = Mapping(
-                matchedFields = matchedFields,
-                unusedColumns = unusedColumns,
-                unusedFields = remainingFields
-        )
-    }
-
-    fun relationTableNames(): List<String> {
-        return relationCollectors.flatMap {
-            val queryTableNames = it.loadAllQuery.tables.map { it.name }
-            if (it.rowAdapter is PojoRowAdapter) {
-                it.rowAdapter.relationTableNames() + queryTableNames
-            } else {
-                queryTableNames
-            }
-        }.distinct()
-    }
-
-    override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
-        relationCollectors.forEach { it.writeInitCode(scope) }
-        mapping.fieldsWithIndices = mapping.matchedFields.map {
-            val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
-            val indexMethod = if (info == null) {
-                "getColumnIndex"
-            } else {
-                "getColumnIndexOrThrow"
-            }
-            scope.builder().addStatement("final $T $L = $L.$L($S)",
-                    TypeName.INT, indexVar, cursorVarName, indexMethod, it.columnName)
-            FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = info != null)
-        }
-    }
-
-    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            FieldReadWriteWriter.readFromCursor(
-                    outVar = outVarName,
-                    outPojo = pojo,
-                    cursorVar = cursorVarName,
-                    fieldsWithIndices = mapping.fieldsWithIndices,
-                    relationCollectors = relationCollectors,
-                    scope = scope)
-        }
-    }
-
-    override fun onCursorFinished(): ((CodeGenScope) -> Unit)? =
-            if (relationCollectors.isEmpty()) {
-                // it is important to return empty to notify that we don't need any post process
-                // task
-                null
-            } else {
-                { scope ->
-                    relationCollectors.forEach { collector ->
-                        collector.writeCollectionCode(scope)
-                    }
-                }
-            }
-
-    data class Mapping(
-            val matchedFields: List<Field>,
-            val unusedColumns: List<String>,
-            val unusedFields: List<Field>) {
-        // set when cursor is ready.
-        lateinit var fieldsWithIndices: List<FieldWithIndex>
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
deleted file mode 100644
index 52f2621..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier
-
-class PositionalDataSourceQueryResultBinder(
-        val listAdapter: ListQueryResultAdapter?,
-        val tableNames: Set<String>) : QueryResultBinder(listAdapter) {
-    val itemTypeName: TypeName = listAdapter?.rowAdapter?.out?.typeName() ?: TypeName.OBJECT
-    val typeName: ParameterizedTypeName = ParameterizedTypeName.get(
-            RoomTypeNames.LIMIT_OFFSET_DATA_SOURCE, itemTypeName)
-    override fun convertAndReturn(roomSQLiteQueryVar: String,
-                                  canReleaseQuery: Boolean,
-                                  dbField: FieldSpec,
-                                  inTransaction: Boolean,
-                                  scope: CodeGenScope) {
-        // first comma for table names comes from the string since it might be empty in which case
-        // we don't need a comma. If list is empty, this prevents generating bad code (it is still
-        // an error to have empty list but that is already reported while item is processed)
-        val tableNamesList = tableNames.joinToString("") { ", \"$it\"" }
-        val spec = TypeSpec.anonymousClassBuilder("$N, $L, $L $L",
-                dbField, roomSQLiteQueryVar, inTransaction, tableNamesList).apply {
-            superclass(typeName)
-            addMethod(createConvertRowsMethod(scope))
-        }.build()
-        scope.builder().apply {
-            addStatement("return $L", spec)
-        }
-    }
-
-    private fun createConvertRowsMethod(scope: CodeGenScope): MethodSpec =
-            MethodSpec.methodBuilder("convertRows").apply {
-                addAnnotation(Override::class.java)
-                addModifiers(Modifier.PROTECTED)
-                returns(ParameterizedTypeName.get(CommonTypeNames.LIST, itemTypeName))
-                val cursorParam = ParameterSpec.builder(AndroidTypeNames.CURSOR, "cursor")
-                        .build()
-                addParameter(cursorParam)
-                val resultVar = scope.getTmpVar("_res")
-                val rowsScope = scope.fork()
-                listAdapter?.convert(resultVar, cursorParam.name, rowsScope)
-                addCode(rowsScope.builder().build())
-                addStatement("return $L", resultVar)
-            }.build()
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultAdapter.kt
deleted file mode 100644
index 2f6ca75..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultAdapter.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.solver.CodeGenScope
-
-/**
- * Gets a Cursor and converts it into the return type of a method annotated with @Query.
- */
-abstract class QueryResultAdapter(val rowAdapter: RowAdapter?) {
-    abstract fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope)
-    fun accessedTableNames(): List<String> {
-        return (rowAdapter as? PojoRowAdapter)?.relationTableNames() ?: emptyList<String>()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
deleted file mode 100644
index 9fff325..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/QueryResultBinder.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.FieldSpec
-
-/**
- * Connects the query, db and the ResultAdapter.
- * <p>
- * The default implementation is InstantResultBinder. If the query is deferred rather than executed
- * directly, such alternative implementations can be implement using this interface (e.g. LiveData,
- * Rx, caching etc)
- */
-abstract class QueryResultBinder(val adapter: QueryResultAdapter?) {
-    /**
-     * receives the sql, bind args and adapter and generates the code that runs the query
-     * and returns the result.
-     */
-    abstract fun convertAndReturn(
-            roomSQLiteQueryVar: String,
-            canReleaseQuery: Boolean, // false if query is provided by the user
-            dbField: FieldSpec,
-            inTransaction: Boolean,
-            scope: CodeGenScope)
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RowAdapter.kt
deleted file mode 100644
index cb25d97..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RowAdapter.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.lang.model.type.TypeMirror
-
-/**
- * Converts a row of a cursor result into an Entity or a primitive.
- * <p>
- * An instance of this is created for each usage so that it can keep local variables.
- */
-abstract class RowAdapter(val out: TypeMirror) {
-    /**
-     * Called when cursor variable is ready, good place to put initialization code.
-     */
-    open fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {}
-
-    /**
-     * Called to convert a single row.
-     */
-    abstract fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope)
-
-    /**
-     * Called when the cursor is finished. It is important to return null if no operation is
-     * necessary so that caller can understand that we can do lazy loading.
-     */
-    open fun onCursorFinished(): ((scope: CodeGenScope) -> Unit)? = null
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt
deleted file mode 100644
index a91773d..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/RxCallableQueryResultBinder.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomRxJava2TypeNames
-import android.arch.persistence.room.ext.RxJava2TypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier
-import javax.lang.model.type.TypeMirror
-
-/**
- * Generic Result binder for Rx classes that accept a callable.
- */
-class RxCallableQueryResultBinder(val rxType: RxType,
-                                  val typeArg: TypeMirror,
-                                  adapter: QueryResultAdapter?)
-    : QueryResultBinder(adapter) {
-    override fun convertAndReturn(roomSQLiteQueryVar: String,
-                                  canReleaseQuery: Boolean,
-                                  dbField: FieldSpec,
-                                  inTransaction: Boolean,
-                                  scope: CodeGenScope) {
-        val callable = TypeSpec.anonymousClassBuilder("").apply {
-            val typeName = typeArg.typeName()
-            superclass(ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
-                    typeName))
-            addMethod(createCallMethod(
-                    roomSQLiteQueryVar = roomSQLiteQueryVar,
-                    canReleaseQuery = canReleaseQuery,
-                    dbField = dbField,
-                    inTransaction = inTransaction,
-                    scope = scope))
-        }.build()
-        scope.builder().apply {
-            addStatement("return $T.fromCallable($L)", rxType.className, callable)
-        }
-    }
-
-    private fun createCallMethod(roomSQLiteQueryVar: String,
-                                 canReleaseQuery: Boolean,
-                                 dbField: FieldSpec,
-                                 inTransaction: Boolean,
-                                 scope: CodeGenScope): MethodSpec {
-        val adapterScope = scope.fork()
-        return MethodSpec.methodBuilder("call").apply {
-            returns(typeArg.typeName())
-            addException(Exception::class.typeName())
-            addModifiers(Modifier.PUBLIC)
-            addAnnotation(Override::class.java)
-            val transactionWrapper = if (inTransaction) {
-                transactionWrapper(dbField)
-            } else {
-                null
-            }
-            transactionWrapper?.beginTransactionWithControlFlow()
-            val outVar = scope.getTmpVar("_result")
-            val cursorVar = scope.getTmpVar("_cursor")
-            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
-                    dbField, roomSQLiteQueryVar)
-            beginControlFlow("try").apply {
-                adapter?.convert(outVar, cursorVar, adapterScope)
-                addCode(adapterScope.generate())
-                if (!rxType.canBeNull) {
-                    beginControlFlow("if($L == null)", outVar).apply {
-                        addStatement("throw new $T($S + $L.getSql())",
-                                RoomRxJava2TypeNames.RX_EMPTY_RESULT_SET_EXCEPTION,
-                                "Query returned empty result set: ",
-                                roomSQLiteQueryVar)
-                    }
-                    endControlFlow()
-                }
-                transactionWrapper?.commitTransaction()
-                addStatement("return $L", outVar)
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$L.close()", cursorVar)
-                if (canReleaseQuery) {
-                    addStatement("$L.release()", roomSQLiteQueryVar)
-                }
-            }
-            endControlFlow()
-            transactionWrapper?.endTransactionWithControlFlow()
-        }.build()
-    }
-
-    enum class RxType(val className: ClassName, val canBeNull: Boolean) {
-        SINGLE(RxJava2TypeNames.SINGLE, canBeNull = false),
-        MAYBE(RxJava2TypeNames.MAYBE, canBeNull = true);
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleColumnRowAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleColumnRowAdapter.kt
deleted file mode 100644
index 8237177..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleColumnRowAdapter.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.solver.types.CursorValueReader
-
-/**
- * Wraps a row adapter when there is only 1 item  with 1 column in the response.
- */
-class SingleColumnRowAdapter(val reader: CursorValueReader) : RowAdapter(reader.typeMirror()) {
-    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        reader.readFromCursor(outVarName, cursorVarName, "0", scope)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleEntityQueryResultAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleEntityQueryResultAdapter.kt
deleted file mode 100644
index 3086894..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/SingleEntityQueryResultAdapter.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import defaultValue
-
-/**
- * Wraps a row adapter when there is only 1 item in the result
- */
-class SingleEntityQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
-    val type = rowAdapter.out
-    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            rowAdapter?.onCursorReady(cursorVarName, scope)
-            addStatement("final $T $L", type.typeName(), outVarName)
-            beginControlFlow("if($L.moveToFirst())", cursorVarName)
-                rowAdapter?.convert(outVarName, cursorVarName, scope)
-            nextControlFlow("else").apply {
-                addStatement("$L = $L", outVarName, rowAdapter?.out?.defaultValue())
-            }
-            endControlFlow()
-            rowAdapter?.onCursorFinished()?.invoke(scope)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TransactionWrapper.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TransactionWrapper.kt
deleted file mode 100644
index b48f179..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/query/result/TransactionWrapper.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query.result
-
-import android.arch.persistence.room.ext.N
-import com.squareup.javapoet.CodeBlock
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-
-/**
- * helper class to create correct transaction code.
- */
-interface TransactionWrapper {
-    fun beginTransactionWithControlFlow()
-    fun commitTransaction()
-    fun endTransactionWithControlFlow()
-}
-
-fun MethodSpec.Builder.transactionWrapper(dbField: FieldSpec) = object : TransactionWrapper {
-    override fun beginTransactionWithControlFlow() {
-        addStatement("$N.beginTransaction()", dbField)
-        beginControlFlow("try")
-    }
-
-    override fun commitTransaction() {
-        addStatement("$N.setTransactionSuccessful()", dbField)
-    }
-
-    override fun endTransactionWithControlFlow() {
-        nextControlFlow("finally")
-        addStatement("$N.endTransaction()", dbField)
-        endControlFlow()
-    }
-}
-
-fun CodeBlock.Builder.transactionWrapper(dbField: FieldSpec) = object : TransactionWrapper {
-    override fun beginTransactionWithControlFlow() {
-        addStatement("$N.beginTransaction()", dbField)
-        beginControlFlow("try")
-    }
-
-    override fun commitTransaction() {
-        addStatement("$N.setTransactionSuccessful()", dbField)
-    }
-
-    override fun endTransactionWithControlFlow() {
-        nextControlFlow("finally")
-        addStatement("$N.endTransaction()", dbField)
-        endControlFlow()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedBooleanToBoxedIntConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
deleted file mode 100644
index 70030a8..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.annotation.processing.ProcessingEnvironment
-
-/**
- * int to boolean adapter.
- */
-object BoxedBooleanToBoxedIntConverter {
-    fun create(processingEnvironment: ProcessingEnvironment): List<TypeConverter> {
-        val tBoolean = processingEnvironment.elementUtils.getTypeElement("java.lang.Boolean")
-                .asType()
-        val tInt = processingEnvironment.elementUtils.getTypeElement("java.lang.Integer")
-                .asType()
-        return listOf(
-                object : TypeConverter(tBoolean, tInt) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().addStatement("$L = $L == null ? null : ($L ? 1 : 0)",
-                                outputVarName, inputVarName, inputVarName)
-                    }
-                },
-                object : TypeConverter(tInt, tBoolean) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().addStatement("$L = $L == null ? null : $L != 0",
-                                outputVarName, inputVarName, inputVarName)
-                    }
-                }
-        )
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt
deleted file mode 100644
index debc59a..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.solver.CodeGenScope
-import com.google.auto.common.MoreTypes
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.type.TypeMirror
-
-/**
- * Adapters for all boxed primitives that has direct cursor mappings.
- */
-open class BoxedPrimitiveColumnTypeAdapter(
-        boxed: TypeMirror,
-        val primitiveAdapter: PrimitiveColumnTypeAdapter
-) : ColumnTypeAdapter(boxed, primitiveAdapter.typeAffinity) {
-    companion object {
-        fun createBoxedPrimitiveAdapters(
-                processingEnvironment: ProcessingEnvironment,
-                primitiveAdapters: List<PrimitiveColumnTypeAdapter>
-        ): List<ColumnTypeAdapter> {
-
-            return primitiveAdapters.map {
-                BoxedPrimitiveColumnTypeAdapter(
-                        processingEnvironment.typeUtils
-                                .boxedClass(MoreTypes.asPrimitiveType(it.out)).asType(),
-                        it
-                )
-            }
-        }
-    }
-
-    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
-                            scope: CodeGenScope) {
-        scope.builder().apply {
-            beginControlFlow("if ($L == null)", valueVarName).apply {
-                addStatement("$L.bindNull($L)", stmtName, indexVarName)
-            }
-            nextControlFlow("else").apply {
-                primitiveAdapter.bindToStmt(stmtName, indexVarName, valueVarName, scope)
-            }
-            endControlFlow()
-        }
-    }
-
-    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
-                                scope: CodeGenScope) {
-        scope.builder().apply {
-            beginControlFlow("if ($L.isNull($L))", cursorVarName, indexVarName).apply {
-                addStatement("$L = null", outVarName)
-            }
-            nextControlFlow("else").apply {
-                primitiveAdapter.readFromCursor(outVarName, cursorVarName, indexVarName, scope)
-            }
-            endControlFlow()
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ByteArrayColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ByteArrayColumnTypeAdapter.kt
deleted file mode 100644
index 5de76de..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ByteArrayColumnTypeAdapter.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.type.TypeKind
-
-class ByteArrayColumnTypeAdapter(env: ProcessingEnvironment) : ColumnTypeAdapter(
-        out = env.typeUtils.getArrayType(env.typeUtils.getPrimitiveType(TypeKind.BYTE)),
-        typeAffinity = SQLTypeAffinity.BLOB) {
-    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
-                                scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("$L = $L.getBlob($L)", outVarName, cursorVarName, indexVarName)
-    }
-
-    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
-                            scope: CodeGenScope) {
-        scope.builder().apply {
-            beginControlFlow("if ($L == null)", valueVarName)
-                    .addStatement("$L.bindNull($L)", stmtName, indexVarName)
-            nextControlFlow("else")
-                    .addStatement("$L.bindBlob($L, $L)", stmtName, indexVarName, valueVarName)
-            endControlFlow()
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ColumnTypeAdapter.kt
deleted file mode 100644
index d82dc79..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/ColumnTypeAdapter.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import com.squareup.javapoet.TypeName
-import javax.lang.model.type.TypeMirror
-
-/**
- * A code generator that can read a field from Cursor and write a field to a Statement
- */
-abstract class ColumnTypeAdapter(val out: TypeMirror, val typeAffinity: SQLTypeAffinity) :
-        StatementValueBinder, CursorValueReader {
-    val outTypeName: TypeName by lazy { TypeName.get(out) }
-    override fun typeMirror() = out
-    override fun affinity(): SQLTypeAffinity = typeAffinity
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeAdapter.kt
deleted file mode 100644
index a8678ff..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeAdapter.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.lang.model.type.TypeMirror
-
-/**
- * A column adapter that uses a type converter to do the conversion. The type converter may be
- * a composite one.
- */
-class CompositeAdapter(out: TypeMirror, val columnTypeAdapter: ColumnTypeAdapter,
-                       val intoStatementConverter: TypeConverter?,
-                       val fromCursorConverter: TypeConverter?)
-    : ColumnTypeAdapter(out, columnTypeAdapter.typeAffinity) {
-    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
-                                scope: CodeGenScope) {
-        if (fromCursorConverter == null) {
-            return
-        }
-        scope.builder().apply {
-            val tmpCursorValue = scope.getTmpVar()
-            addStatement("final $T $L", columnTypeAdapter.outTypeName, tmpCursorValue)
-            columnTypeAdapter.readFromCursor(tmpCursorValue, cursorVarName, indexVarName, scope)
-            fromCursorConverter.convert(tmpCursorValue, outVarName, scope)
-        }
-    }
-
-    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
-                            scope: CodeGenScope) {
-        if (intoStatementConverter == null) {
-            return
-        }
-        scope.builder().apply {
-            val tmpVar = scope.getTmpVar()
-            addStatement("final $T $L", columnTypeAdapter.out, tmpVar)
-            intoStatementConverter.convert(valueVarName, tmpVar, scope)
-            columnTypeAdapter.bindToStmt(stmtName, indexVarName, tmpVar, scope)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeTypeConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeTypeConverter.kt
deleted file mode 100644
index b02709a..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CompositeTypeConverter.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-
-/**
- * combines 2 type converters
- */
-class CompositeTypeConverter(val conv1: TypeConverter, val conv2: TypeConverter) : TypeConverter(
-        conv1.from, conv2.to) {
-    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            val tmp = scope.getTmpVar()
-            addStatement("final $T $L", conv1.to.typeName(), tmp)
-            conv1.convert(inputVarName, tmp, scope)
-            conv2.convert(tmp, outputVarName, scope)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CursorValueReader.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CursorValueReader.kt
deleted file mode 100644
index 9293143..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CursorValueReader.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.lang.model.type.TypeMirror
-
-/**
- * Reads value from a cursor at the given index.
- * see: StatementValueBinder
- */
-interface CursorValueReader {
-    fun affinity(): SQLTypeAffinity
-    fun typeMirror(): TypeMirror
-    fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
-                                scope: CodeGenScope)
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CustomTypeConverterWrapper.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CustomTypeConverterWrapper.kt
deleted file mode 100644
index c127203..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/CustomTypeConverterWrapper.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.CustomTypeConverter
-import android.arch.persistence.room.writer.ClassWriter
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import javax.lang.model.element.Modifier
-
-/**
- * Wraps a type converter specified by the developer and forwards calls to it.
- */
-class CustomTypeConverterWrapper(val custom: CustomTypeConverter)
-    : TypeConverter(custom.from, custom.to) {
-
-    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
-            if (custom.isStatic) {
-                addStatement("$L = $T.$L($L)",
-                        outputVarName, custom.typeName,
-                        custom.methodName, inputVarName)
-            } else {
-                addStatement("$L = $N.$L($L)",
-                        outputVarName, typeConverter(scope),
-                        custom.methodName, inputVarName)
-            }
-        }
-    }
-
-    fun typeConverter(scope: CodeGenScope): FieldSpec {
-        val baseName = (custom.typeName as ClassName).simpleName().decapitalize()
-        return scope.writer.getOrCreateField(object : ClassWriter.SharedFieldSpec(
-                baseName, custom.typeName) {
-            override fun getUniqueKey(): String {
-                return "converter_${custom.typeName}"
-            }
-
-            override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
-                builder.addModifiers(Modifier.PRIVATE)
-                builder.addModifiers(Modifier.FINAL)
-                builder.initializer("new $T()", custom.typeName)
-            }
-        })
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/NoOpConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/NoOpConverter.kt
deleted file mode 100644
index c37698b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/NoOpConverter.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.lang.model.type.TypeMirror
-
-/**
- * Yes, we need this when user input is the same as the desired output.
- * <p>
- * Each query parameter receives an adapter that converts it into a String (or String[]). This
- * TypeAdapter basically serves as a wrapper for converting String parameter into the String[] of
- * the query. Not having this would require us to special case handle String, String[], List<String>
- * etc.
- */
-class NoOpConverter(type: TypeMirror) : TypeConverter(
-        type, type) {
-    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("$L = $L", outputVarName, inputVarName)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveBooleanToIntConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveBooleanToIntConverter.kt
deleted file mode 100644
index c65df5c..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveBooleanToIntConverter.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.type.TypeKind.BOOLEAN
-import javax.lang.model.type.TypeKind.INT
-
-/**
- * int to boolean adapter.
- */
-object PrimitiveBooleanToIntConverter {
-    fun create(processingEnvironment: ProcessingEnvironment): List<TypeConverter> {
-        val tBoolean = processingEnvironment.typeUtils.getPrimitiveType(BOOLEAN)
-        val tInt = processingEnvironment.typeUtils.getPrimitiveType(INT)
-        return listOf(
-                object : TypeConverter(tBoolean, tInt) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().addStatement("$L = $L ? 1 : 0", outputVarName, inputVarName)
-                    }
-                },
-                object : TypeConverter(tInt, tBoolean) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().addStatement("$L = $L != 0", outputVarName, inputVarName)
-                    }
-                })
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveColumnTypeAdapter.kt
deleted file mode 100644
index 69cc0f8..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/PrimitiveColumnTypeAdapter.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.parser.SQLTypeAffinity.REAL
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.type.PrimitiveType
-import javax.lang.model.type.TypeKind.BYTE
-import javax.lang.model.type.TypeKind.CHAR
-import javax.lang.model.type.TypeKind.DOUBLE
-import javax.lang.model.type.TypeKind.FLOAT
-import javax.lang.model.type.TypeKind.INT
-import javax.lang.model.type.TypeKind.LONG
-import javax.lang.model.type.TypeKind.SHORT
-
-/**
- * Adapters for all primitives that has direct cursor mappings.
- */
-open class PrimitiveColumnTypeAdapter(out: PrimitiveType,
-                                      val cursorGetter: String,
-                                      val stmtSetter: String,
-                                      typeAffinity: SQLTypeAffinity)
-        : ColumnTypeAdapter(out, typeAffinity) {
-    val cast = if (cursorGetter == "get${out.typeName().toString().capitalize()}")
-                    ""
-                else
-                    "(${out.typeName()}) "
-
-    companion object {
-        fun createPrimitiveAdapters(
-                processingEnvironment: ProcessingEnvironment
-        ): List<PrimitiveColumnTypeAdapter> {
-            return listOf(
-                    Triple(INT, "getInt", "bindLong"),
-                    Triple(SHORT, "getShort", "bindLong"),
-                    Triple(BYTE, "getShort", "bindLong"),
-                    Triple(LONG, "getLong", "bindLong"),
-                    Triple(CHAR, "getInt", "bindLong"),
-                    Triple(FLOAT, "getFloat", "bindDouble"),
-                    Triple(DOUBLE, "getDouble", "bindDouble")
-            ).map {
-                PrimitiveColumnTypeAdapter(
-                        out = processingEnvironment.typeUtils.getPrimitiveType(it.first),
-                        cursorGetter = it.second,
-                        stmtSetter = it.third,
-                        typeAffinity = when (it.first) {
-                            INT, SHORT, BYTE, LONG, CHAR -> SQLTypeAffinity.INTEGER
-                            FLOAT, DOUBLE -> REAL
-                            else -> throw IllegalArgumentException("invalid type")
-                        }
-                )
-            }
-        }
-    }
-
-    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
-                            scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("$L.$L($L, $L)", stmtName, stmtSetter, indexVarName, valueVarName)
-    }
-
-    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
-                                scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("$L = $L$L.$L($L)", outVarName, cast, cursorVarName,
-                        cursorGetter, indexVarName)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StatementValueBinder.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StatementValueBinder.kt
deleted file mode 100644
index d83c145..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StatementValueBinder.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.lang.model.type.TypeMirror
-
-/**
- * Binds a value into a statement
- * see: CursorValueReader
- */
-interface StatementValueBinder {
-    fun typeMirror(): TypeMirror
-    fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
-                            scope: CodeGenScope)
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StringColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StringColumnTypeAdapter.kt
deleted file mode 100644
index 34ca61f..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/StringColumnTypeAdapter.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.parser.SQLTypeAffinity.TEXT
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.annotation.processing.ProcessingEnvironment
-
-class StringColumnTypeAdapter(processingEnvironment: ProcessingEnvironment)
-    : ColumnTypeAdapter((processingEnvironment.elementUtils.getTypeElement(
-        String::class.java.canonicalName)).asType(), TEXT) {
-    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
-                                scope: CodeGenScope) {
-        scope.builder()
-                .addStatement("$L = $L.getString($L)", outVarName, cursorVarName, indexVarName)
-    }
-
-    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
-                            scope: CodeGenScope) {
-        scope.builder().apply {
-            beginControlFlow("if ($L == null)", valueVarName)
-                    .addStatement("$L.bindNull($L)", stmtName, indexVarName)
-            nextControlFlow("else")
-                    .addStatement("$L.bindString($L, $L)", stmtName, indexVarName, valueVarName)
-            endControlFlow()
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/TypeConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/TypeConverter.kt
deleted file mode 100644
index 67b70dc..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/solver/types/TypeConverter.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.types
-
-import android.arch.persistence.room.solver.CodeGenScope
-import javax.lang.model.type.TypeMirror
-
-/**
- * A code generator that can convert from 1 type to another
- */
-abstract class TypeConverter(val from: TypeMirror, val to: TypeMirror) {
-    abstract fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope)
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/ColumnInfo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/ColumnInfo.kt
deleted file mode 100644
index 652860b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/ColumnInfo.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.verifier
-
-import android.arch.persistence.room.parser.SQLTypeAffinity
-
-/**
- * Represents a column in a query response
- */
-data class ColumnInfo(val name: String, val type: SQLTypeAffinity)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerificaitonErrors.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerificaitonErrors.kt
deleted file mode 100644
index 6b133b4..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerificaitonErrors.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.verifier
-
-import java.sql.SQLException
-
-object DatabaseVerificaitonErrors {
-    private val CANNOT_CREATE_TABLE: String = "Create table statement had an error: %s"
-    fun cannotCreateTable(exception: SQLException): String {
-        return CANNOT_CREATE_TABLE.format(exception.message)
-    }
-
-    private val CANNOT_VERIFY_QUERY: String = "There is a problem with the query: %s"
-    fun cannotVerifyQuery(exception: SQLException): String {
-        return CANNOT_VERIFY_QUERY.format(exception.message)
-    }
-
-    private val CANNOT_CREATE_SQLITE_CONNECTION: String = "Room cannot create an SQLite" +
-            " connection to verify the queries. Query verification will be disabled. Error: %s"
-    fun cannotCreateConnection(exception: Exception): String {
-        return CANNOT_CREATE_SQLITE_CONNECTION.format(exception.message)
-    }
-
-    val CANNOT_GET_TMP_JAVA_DIR = "Cannot read tmp java dir which is necessary to load sqlite" +
-            " lib. Database SQL verification will be disabled"
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerifier.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerifier.kt
deleted file mode 100644
index 4e1c639..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/DatabaseVerifier.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.verifier
-
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.Warning
-import columnInfo
-import org.sqlite.JDBC
-import java.io.File
-import java.sql.Connection
-import java.sql.DriverManager
-import java.sql.SQLException
-import java.util.UUID
-import java.util.regex.Pattern
-import javax.lang.model.element.Element
-
-/**
- * Builds an in-memory version of the database and verifies the queries against it.
- * This class is also used to resolve the return types.
- */
-class DatabaseVerifier private constructor(
-        val connection: Connection, val context: Context, val entities: List<Entity>) {
-    companion object {
-        private const val CONNECTION_URL = "jdbc:sqlite::memory:"
-        /**
-         * Taken from:
-         * https://github.com/robolectric/robolectric/blob/master/shadows/framework/
-         * src/main/java/org/robolectric/shadows/ShadowSQLiteConnection.java#L94
-         *
-         * This is actually not accurate because it might swap anything since it does not parse
-         * SQL. That being said, for the verification purposes, it does not matter and clearly
-         * much easier than parsing and rebuilding the query.
-         */
-        private val COLLATE_LOCALIZED_UNICODE_PATTERN = Pattern.compile(
-                "\\s+COLLATE\\s+(LOCALIZED|UNICODE)", Pattern.CASE_INSENSITIVE)
-
-        init {
-            // see: https://github.com/xerial/sqlite-jdbc/issues/97
-            val tmpDir = System.getProperty("java.io.tmpdir")
-            if (tmpDir != null) {
-                val outDir = File(tmpDir, "room-${UUID.randomUUID()}")
-                outDir.mkdirs()
-                outDir.deleteOnExit()
-                System.setProperty("org.sqlite.tmpdir", outDir.absolutePath)
-                // dummy call to trigger JDBC initialization so that we can unregister it
-                JDBC.isValidURL(CONNECTION_URL)
-                unregisterDrivers()
-            }
-        }
-
-        /**
-         * Tries to create a verifier but returns null if it cannot find the driver.
-         */
-        fun create(context: Context, element: Element, entities: List<Entity>): DatabaseVerifier? {
-            return try {
-                val connection = JDBC.createConnection(CONNECTION_URL, java.util.Properties())
-                DatabaseVerifier(connection, context, entities)
-            } catch (ex: Exception) {
-                context.logger.w(Warning.CANNOT_CREATE_VERIFICATION_DATABASE, element,
-                        DatabaseVerificaitonErrors.cannotCreateConnection(ex))
-                null
-            }
-        }
-
-        /**
-         * Unregisters the JDBC driver. If we don't do this, we'll leak the driver which leaks a
-         * whole class loader.
-         * see: https://github.com/xerial/sqlite-jdbc/issues/267
-         * see: https://issuetracker.google.com/issues/62473121
-         */
-        private fun unregisterDrivers() {
-            try {
-                DriverManager.getDriver(CONNECTION_URL)?.let {
-                    DriverManager.deregisterDriver(it)
-                }
-            } catch (t: Throwable) {
-                System.err.println("Room: cannot unregister driver ${t.message}")
-            }
-        }
-    }
-    init {
-        entities.forEach { entity ->
-            val stmt = connection.createStatement()
-            stmt.executeUpdate(stripLocalizeCollations(entity.createTableQuery))
-        }
-    }
-
-    fun analyze(sql: String): QueryResultInfo {
-        return try {
-            val stmt = connection.prepareStatement(stripLocalizeCollations(sql))
-            QueryResultInfo(stmt.columnInfo())
-        } catch (ex: SQLException) {
-            QueryResultInfo(emptyList(), ex)
-        }
-    }
-
-    private fun stripLocalizeCollations(sql: String) =
-        COLLATE_LOCALIZED_UNICODE_PATTERN.matcher(sql).replaceAll(" COLLATE NOCASE")
-
-    fun closeConnection(context: Context) {
-        if (!connection.isClosed) {
-            try {
-                connection.close()
-            } catch (t: Throwable) {
-                //ignore.
-                context.logger.d("failed to close the database connection ${t.message}")
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/QueryResultInfo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/QueryResultInfo.kt
deleted file mode 100644
index 41b348f..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/QueryResultInfo.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.verifier
-
-import java.sql.SQLException
-
-/**
- * Represents the result of a query.
- * <p>
- * This information is obtained by preparing the query against an in memory database at compile
- * time.
- */
-data class QueryResultInfo(val columns: List<ColumnInfo>, val error: SQLException? = null)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/jdbc_ext.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/jdbc_ext.kt
deleted file mode 100644
index 47b9442..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/verifier/jdbc_ext.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.verifier.ColumnInfo
-import java.sql.PreparedStatement
-import java.sql.ResultSet
-import java.sql.ResultSetMetaData
-import java.sql.SQLException
-
-internal fun <T> ResultSet.collect(f: (ResultSet) -> T): List<T> {
-    val result = arrayListOf<T>()
-    try {
-        while (next()) {
-            result.add(f.invoke(this))
-        }
-    } finally {
-        close()
-    }
-    return result
-}
-
-private fun <T> PreparedStatement.map(f: (Int, ResultSetMetaData) -> T): List<T> {
-    val columnCount = try {
-        metaData.columnCount
-    } catch (ex: SQLException) {
-        // ignore, no-result query
-        0
-    }
-    // return is separate than data creation because we want to know who throws the exception
-    return (1.rangeTo(columnCount)).map { f(it, metaData) }
-}
-
-internal fun PreparedStatement.columnNames(): List<String> {
-    return map { index, data -> data.getColumnName(index) }
-}
-
-private fun PreparedStatement.tryGetAffinity(columnIndex: Int): SQLTypeAffinity {
-    return try {
-        SQLTypeAffinity.valueOf(metaData.getColumnTypeName(columnIndex).capitalize())
-    } catch (ex: IllegalArgumentException) {
-        SQLTypeAffinity.NULL
-    }
-}
-
-internal fun PreparedStatement.columnInfo(): List<ColumnInfo> {
-    //see: http://sqlite.1065341.n5.nabble.com/Column-order-in-resultset-td23127.html
-    return map { index, data -> ColumnInfo(data.getColumnName(index), tryGetAffinity(index)) }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CallType.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CallType.kt
deleted file mode 100644
index 40cbcc4..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CallType.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-enum class CallType {
-    FIELD,
-    METHOD,
-    CONSTRUCTOR
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Constructor.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Constructor.kt
deleted file mode 100644
index 112c9b4..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Constructor.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import javax.lang.model.element.ExecutableElement
-
-/**
- * For each Entity / Pojo we process has a constructor. It might be the empty constructor or a
- * constructor with fields.
- */
-data class Constructor(val element: ExecutableElement, val params: List<Param>) {
-
-    fun hasField(field: Field): Boolean {
-        return params.any {
-            when (it) {
-                is FieldParam -> it.field === field
-                is EmbeddedParam -> it.embedded.field === field
-                else -> false
-            }
-        }
-    }
-
-    class FieldParam(val field: Field) : Param(ParamType.FIELD) {
-        override fun log(): String = field.getPath()
-    }
-
-    class EmbeddedParam(val embedded: EmbeddedField) : Param(ParamType.EMBEDDED) {
-        override fun log(): String = embedded.field.getPath()
-    }
-
-    abstract class Param(val type: ParamType) {
-        abstract fun log(): String
-    }
-
-    enum class ParamType {
-        FIELD,
-        EMBEDDED
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CustomTypeConverter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CustomTypeConverter.kt
deleted file mode 100644
index 3bbae96..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/CustomTypeConverter.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.hasAnyOf
-import android.arch.persistence.room.ext.typeName
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier
-import javax.lang.model.type.TypeMirror
-
-/**
- * Generated when we parse a method annotated with TypeConverter.
- */
-data class CustomTypeConverter(val type: TypeMirror,
-                               val method: ExecutableElement,
-                               val from: TypeMirror, val to: TypeMirror) {
-    val typeName: TypeName by lazy { type.typeName() }
-    val fromTypeName: TypeName by lazy { from.typeName() }
-    val toTypeName: TypeName by lazy { to.typeName() }
-    val methodName by lazy { method.simpleName.toString() }
-    val isStatic by lazy { method.hasAnyOf(Modifier.STATIC) }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Dao.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Dao.kt
deleted file mode 100644
index 774624c..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Dao.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-
-data class Dao(
-        val element: TypeElement, val type: DeclaredType,
-        val queryMethods: List<QueryMethod>,
-        val rawQueryMethods: List<RawQueryMethod>,
-        val insertionMethods: List<InsertionMethod>,
-        val deletionMethods: List<DeletionMethod>,
-        val updateMethods: List<UpdateMethod>,
-        val transactionMethods: List<TransactionMethod>,
-        val constructorParamType: TypeName?) {
-    // parsed dao might have a suffix if it is used in multiple databases.
-    private var suffix: String? = null
-
-    fun setSuffix(newSuffix: String) {
-        if (this.suffix != null) {
-            throw IllegalStateException("cannot set suffix twice")
-        }
-        this.suffix = if (newSuffix == "") "" else "_$newSuffix"
-    }
-
-    val typeName: ClassName by lazy { ClassName.get(element) }
-
-    val shortcutMethods: List<ShortcutMethod> by lazy {
-        deletionMethods + updateMethods
-    }
-
-    private val implClassName by lazy {
-        if (suffix == null) {
-            suffix = ""
-        }
-        val path = arrayListOf<String>()
-        var enclosing = element.enclosingElement
-        while (enclosing is TypeElement) {
-            path.add(ClassName.get(enclosing as TypeElement).simpleName())
-            enclosing = enclosing.enclosingElement
-        }
-        path.reversed().joinToString("_") + "${typeName.simpleName()}${suffix}_Impl"
-    }
-
-    val implTypeName: ClassName by lazy {
-        ClassName.get(typeName.packageName(), implClassName)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DaoMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DaoMethod.kt
deleted file mode 100644
index bf134ad..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DaoMethod.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import javax.lang.model.element.Element
-
-/**
- * References a method that returns a dao in a Database
- */
-data class DaoMethod(val element: Element, val name: String, val dao: Dao)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Database.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Database.kt
deleted file mode 100644
index 2cffbf3..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Database.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.RoomMasterTable
-import android.arch.persistence.room.migration.bundle.DatabaseBundle
-import android.arch.persistence.room.migration.bundle.SchemaBundle
-import com.squareup.javapoet.ClassName
-import org.apache.commons.codec.digest.DigestUtils
-import java.io.File
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.TypeMirror
-
-/**
- * Holds information about a class annotated with Database.
- */
-data class Database(val element: TypeElement,
-                    val type: TypeMirror,
-                    val entities: List<Entity>,
-                    val daoMethods: List<DaoMethod>,
-                    val version: Int,
-                    val exportSchema: Boolean,
-                    val enableForeignKeys: Boolean) {
-    val typeName: ClassName by lazy { ClassName.get(element) }
-
-    private val implClassName by lazy {
-        "${typeName.simpleNames().joinToString("_")}_Impl"
-    }
-
-    val implTypeName: ClassName by lazy {
-        ClassName.get(typeName.packageName(), implClassName)
-    }
-
-    val bundle by lazy {
-        DatabaseBundle(version, identityHash, entities.map(Entity::toBundle),
-                listOf(RoomMasterTable.CREATE_QUERY,
-                        RoomMasterTable.createInsertQuery(identityHash)))
-    }
-
-    /**
-     * Create a has that identifies this database definition so that at runtime we can check to
-     * ensure developer didn't forget to update the version.
-     */
-    val identityHash: String by lazy {
-        val idKey = SchemaIdentityKey()
-        idKey.appendSorted(entities)
-        idKey.hash()
-    }
-
-    val legacyIdentityHash: String by lazy {
-        val entityDescriptions = entities
-                .sortedBy { it.tableName }
-                .map { it.createTableQuery }
-        val indexDescriptions = entities
-                .flatMap { entity ->
-                    entity.indices.map { index ->
-                        index.createQuery(entity.tableName)
-                    }
-                }
-        val input = (entityDescriptions + indexDescriptions).joinToString("¯\\_(ツ)_/¯")
-        DigestUtils.md5Hex(input)
-    }
-
-    fun exportSchema(file: File) {
-        val schemaBundle = SchemaBundle(SchemaBundle.LATEST_FORMAT, bundle)
-        if (file.exists()) {
-            val existing = file.inputStream().use {
-                SchemaBundle.deserialize(it)
-            }
-            if (existing.isSchemaEqual(schemaBundle)) {
-                return
-            }
-        }
-        SchemaBundle.serialize(schemaBundle, file)
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DeletionMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DeletionMethod.kt
deleted file mode 100644
index e122c85..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/DeletionMethod.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.vo
-
-import javax.lang.model.element.ExecutableElement
-
-class DeletionMethod(
-        element: ExecutableElement,
-        name: String,
-        entities: Map<String, Entity>, returnCount: Boolean,
-        parameters: List<ShortcutQueryParameter>
-) : ShortcutMethod(element, name, entities, returnCount, parameters)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/EmbeddedField.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/EmbeddedField.kt
deleted file mode 100644
index 55539a2..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/EmbeddedField.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.support.annotation.NonNull
-import android.arch.persistence.room.ext.hasAnnotation
-
-/**
- * Used when a field is embedded inside an Entity or Pojo.
- */
-// used in cache matching, must stay as a data class or implement equals
-data class EmbeddedField(val field: Field, val prefix: String = "",
-                         val parent: EmbeddedField?) {
-    val getter by lazy { field.getter }
-    val setter by lazy { field.setter }
-    val nonNull = field.element.hasAnnotation(NonNull::class)
-    lateinit var pojo: Pojo
-    val mRootParent: EmbeddedField by lazy {
-        parent?.mRootParent ?: this
-    }
-
-    fun isDescendantOf(other: EmbeddedField): Boolean {
-        if (parent == other) {
-            return true
-        } else if (parent == null) {
-            return false
-        } else {
-            return parent.isDescendantOf(other)
-        }
-    }
-
-    fun isNonNullRecursively(): Boolean {
-        return field.nonNull && (parent == null || parent.isNonNullRecursively())
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Entity.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Entity.kt
deleted file mode 100644
index cf37bad..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Entity.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.migration.bundle.BundleUtil
-import android.arch.persistence.room.migration.bundle.EntityBundle
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-
-// TODO make data class when move to kotlin 1.1
-class Entity(
-        element: TypeElement, val tableName: String, type: DeclaredType,
-        fields: List<Field>, embeddedFields: List<EmbeddedField>,
-        val primaryKey: PrimaryKey, val indices: List<Index>,
-        val foreignKeys: List<ForeignKey>,
-        constructor: Constructor?)
-    : Pojo(element, type, fields, embeddedFields, emptyList(), constructor), HasSchemaIdentity {
-
-    val createTableQuery by lazy {
-        createTableQuery(tableName)
-    }
-
-    // a string defining the identity of this entity, which can be used for equality checks
-    override fun getIdKey(): String {
-        val identityKey = SchemaIdentityKey()
-        identityKey.append(tableName)
-        identityKey.append(primaryKey)
-        identityKey.appendSorted(fields)
-        identityKey.appendSorted(indices)
-        identityKey.appendSorted(foreignKeys)
-        return identityKey.hash()
-    }
-
-    private fun createTableQuery(tableName: String): String {
-        val definitions = (fields.map {
-            val autoIncrement = primaryKey.autoGenerateId && primaryKey.fields.contains(it)
-            it.databaseDefinition(autoIncrement)
-        } + createPrimaryKeyDefinition() + createForeignKeyDefinitions()).filterNotNull()
-        return "CREATE TABLE IF NOT EXISTS `$tableName` (${definitions.joinToString(", ")})"
-    }
-
-    private fun createForeignKeyDefinitions(): List<String> {
-        return foreignKeys.map { it.databaseDefinition() }
-    }
-
-    private fun createPrimaryKeyDefinition(): String? {
-        return if (primaryKey.fields.isEmpty() || primaryKey.autoGenerateId) {
-            null
-        } else {
-            val keys = primaryKey.fields
-                    .map { "`${it.columnName}`" }
-                    .joinToString(", ")
-            "PRIMARY KEY($keys)"
-        }
-    }
-
-    fun shouldBeDeletedAfter(other: Entity): Boolean {
-        return foreignKeys.any {
-            it.parentTable == other.tableName
-                    && ((!it.deferred && it.onDelete == ForeignKeyAction.NO_ACTION)
-                    || it.onDelete == ForeignKeyAction.RESTRICT)
-        }
-    }
-
-    fun toBundle(): EntityBundle = EntityBundle(
-            tableName,
-            createTableQuery(BundleUtil.TABLE_NAME_PLACEHOLDER),
-            fields.map { it.toBundle() },
-            primaryKey.toBundle(),
-            indices.map { it.toBundle() },
-            foreignKeys.map { it.toBundle() })
-
-    fun isUnique(columns: List<String>): Boolean {
-        return if (primaryKey.columnNames.size == columns.size
-                && primaryKey.columnNames.containsAll(columns)) {
-            true
-        } else {
-            indices.any { index ->
-                index.unique
-                        && index.fields.size == columns.size
-                        && index.columnNames.containsAll(columns)
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Field.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Field.kt
deleted file mode 100644
index 43e0605..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Field.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.isNonNull
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.migration.bundle.FieldBundle
-import android.arch.persistence.room.parser.Collate
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.solver.types.CursorValueReader
-import android.arch.persistence.room.solver.types.StatementValueBinder
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.Element
-import javax.lang.model.type.TypeMirror
-// used in cache matching, must stay as a data class or implement equals
-data class Field(val element: Element, val name: String, val type: TypeMirror,
-                 var affinity: SQLTypeAffinity?,
-                 val collate: Collate? = null,
-                 val columnName: String = name,
-                 /* means that this field does not belong to parent, instead, it belongs to a
-                 * embedded child of the main Pojo*/
-                 val parent: EmbeddedField? = null,
-                 // index might be removed when being merged into an Entity
-                 var indexed: Boolean = false) : HasSchemaIdentity {
-    lateinit var getter: FieldGetter
-    lateinit var setter: FieldSetter
-    // binds the field into a statement
-    var statementBinder: StatementValueBinder? = null
-    // reads this field from a cursor column
-    var cursorValueReader: CursorValueReader? = null
-    val typeName: TypeName by lazy { type.typeName() }
-
-    /** Whether the table column for this field should be NOT NULL */
-    val nonNull = element.isNonNull() && (parent == null || parent.isNonNullRecursively())
-
-    override fun getIdKey(): String {
-        // we don't get the collate information from sqlite so ignoring it here.
-        return "$columnName-${affinity?.name ?: SQLTypeAffinity.TEXT.name}-$nonNull"
-    }
-
-    /**
-     * Used when reporting errors on duplicate names
-     */
-    fun getPath(): String {
-        return if (parent == null) {
-            name
-        } else {
-            "${parent.field.getPath()} > $name"
-        }
-    }
-
-    private val pathWithDotNotation: String by lazy {
-        if (parent == null) {
-            name
-        } else {
-            "${parent.field.pathWithDotNotation}.$name"
-        }
-    }
-
-    /**
-     * List of names that include variations.
-     * e.g. if it is mUser, user is added to the list
-     * or if it is isAdmin, admin is added to the list
-     */
-    val nameWithVariations by lazy {
-        val result = arrayListOf(name)
-        if (name.length > 1) {
-            if (name.startsWith('_')) {
-                result.add(name.substring(1))
-            }
-            if (name.startsWith("m") && name[1].isUpperCase()) {
-                result.add(name.substring(1).decapitalize())
-            }
-
-            if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
-                if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) {
-                    result.add(name.substring(2).decapitalize())
-                }
-                if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) {
-                    result.add(name.substring(3).decapitalize())
-                }
-            }
-        }
-        result
-    }
-
-    val getterNameWithVariations by lazy {
-        nameWithVariations.map { "get${it.capitalize()}" } +
-                if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
-                    nameWithVariations.flatMap {
-                        listOf("is${it.capitalize()}", "has${it.capitalize()}")
-                    }
-                } else {
-                    emptyList()
-                }
-    }
-
-    val setterNameWithVariations by lazy {
-        nameWithVariations.map { "set${it.capitalize()}" }
-    }
-
-    /**
-     * definition to be used in create query
-     */
-    fun databaseDefinition(autoIncrementPKey: Boolean): String {
-        val columnSpec = StringBuilder("")
-        if (autoIncrementPKey) {
-            columnSpec.append(" PRIMARY KEY AUTOINCREMENT")
-        }
-        if (nonNull) {
-            columnSpec.append(" NOT NULL")
-        }
-        if (collate != null) {
-            columnSpec.append(" COLLATE ${collate.name}")
-        }
-        return "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}$columnSpec"
-    }
-
-    fun toBundle(): FieldBundle = FieldBundle(pathWithDotNotation, columnName,
-            affinity?.name ?: SQLTypeAffinity.TEXT.name, nonNull
-    )
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldGetter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldGetter.kt
deleted file mode 100644
index 70a9bc3..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldGetter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import com.squareup.javapoet.CodeBlock
-import com.squareup.javapoet.TypeName
-import javax.lang.model.type.TypeMirror
-
-data class FieldGetter(val name: String, val type: TypeMirror, val callType: CallType) {
-    fun writeGet(ownerVar: String, outVar: String, builder: CodeBlock.Builder) {
-        val stmt = when (callType) {
-            CallType.FIELD -> "final $T $L = $L.$L"
-            CallType.METHOD -> "final $T $L = $L.$L()"
-            CallType.CONSTRUCTOR -> null
-        }
-        stmt?.let {
-            builder.addStatement(stmt, TypeName.get(type), outVar, ownerVar, name)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldSetter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldSetter.kt
deleted file mode 100644
index 5aa1806..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldSetter.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.L
-import com.squareup.javapoet.CodeBlock
-import javax.lang.model.type.TypeMirror
-
-data class FieldSetter(val name: String, val type: TypeMirror, val callType: CallType) {
-    fun writeSet(ownerVar: String, inVar: String, builder: CodeBlock.Builder) {
-        val stmt = when (callType) {
-            CallType.FIELD -> "$L.$L = $L"
-            CallType.METHOD -> "$L.$L($L)"
-            CallType.CONSTRUCTOR -> null
-        }
-        stmt?.let {
-            builder.addStatement(stmt, ownerVar, name, inVar)
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldWithIndex.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldWithIndex.kt
deleted file mode 100644
index b30dcc9..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/FieldWithIndex.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-/**
- * A common value object when we need to associate a Field with an Index
- * variable.
- * <p>
- * If we are sure that the field will be there at compile time, we set it to always Exists so that
- * the generated code does not check for -1 column indices.
- */
-data class FieldWithIndex(val field: Field, val indexVar: String, val alwaysExists: Boolean) {
-    companion object {
-        fun byOrder(fields: List<Field>): List<FieldWithIndex> {
-            return fields.mapIndexed { index, field ->
-                FieldWithIndex(field = field,
-                        indexVar = "${index + 1}",
-                        alwaysExists = true)
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKey.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKey.kt
deleted file mode 100644
index 833de97..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKey.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.migration.bundle.ForeignKeyBundle
-
-/**
- * Keeps information about a foreign key.
- */
-data class ForeignKey(val parentTable: String,
-                      val parentColumns: List<String>,
-                      val childFields: List<Field>,
-                      val onDelete: ForeignKeyAction,
-                      val onUpdate: ForeignKeyAction,
-                      val deferred: Boolean) : HasSchemaIdentity {
-    override fun getIdKey(): String {
-        return parentTable +
-                "-${parentColumns.joinToString(",")}" +
-                "-${childFields.joinToString(",") {it.columnName}}" +
-                "-${onDelete.sqlName}" +
-                "-${onUpdate.sqlName}" +
-                "-$deferred"
-    }
-
-    fun databaseDefinition(): String {
-        return "FOREIGN KEY(${joinEscaped(childFields.map { it.columnName })})" +
-                " REFERENCES `$parentTable`(${joinEscaped(parentColumns)})" +
-                " ON UPDATE ${onUpdate.sqlName}" +
-                " ON DELETE ${onDelete.sqlName}" +
-                " ${deferredDeclaration()}"
-    }
-
-    private fun deferredDeclaration(): String {
-        return if (deferred) {
-            "DEFERRABLE INITIALLY DEFERRED"
-        } else {
-            ""
-        }
-    }
-
-    private fun joinEscaped(values: Iterable<String>) = values.joinToString(", ") { "`$it`" }
-
-    fun toBundle(): ForeignKeyBundle = ForeignKeyBundle(
-            parentTable, onDelete.sqlName, onUpdate.sqlName,
-            childFields.map { it.columnName },
-            parentColumns
-    )
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKeyAction.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKeyAction.kt
deleted file mode 100644
index c6ed9da..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ForeignKeyAction.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ForeignKey
-
-/**
- * Compiler representation of ForeignKey#Action.
- */
-enum class ForeignKeyAction(val annotationValue: Int, val sqlName: String) {
-    NO_ACTION(ForeignKey.NO_ACTION, "NO ACTION"),
-    RESTRICT(ForeignKey.RESTRICT, "RESTRICT"),
-    SET_NULL(ForeignKey.SET_NULL, "SET NULL"),
-    SET_DEFAULT(ForeignKey.SET_DEFAULT, "SET DEFAULT"),
-    CASCADE(ForeignKey.CASCADE, "CASCADE");
-    companion object {
-        private val mapping by lazy {
-            ForeignKeyAction.values().associateBy { it.annotationValue }
-        }
-        fun fromAnnotationValue(value: Int?) = mapping[value]
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Index.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Index.kt
deleted file mode 100644
index b4952cf..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Index.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.migration.bundle.BundleUtil
-import android.arch.persistence.room.migration.bundle.IndexBundle
-
-/**
- * Represents a processed index.
- */
-data class Index(val name: String, val unique: Boolean, val fields: List<Field>) :
-        HasSchemaIdentity {
-    companion object {
-        // should match the value in TableInfo.Index.DEFAULT_PREFIX
-        const val DEFAULT_PREFIX = "index_"
-    }
-
-    override fun getIdKey(): String {
-        return "$unique-$name-${fields.joinToString(",") { it.columnName }}"
-    }
-
-    fun createQuery(tableName: String): String {
-        val uniqueSQL = if (unique) {
-            "UNIQUE"
-        } else {
-            ""
-        }
-        return """
-            CREATE $uniqueSQL INDEX `$name`
-            ON `$tableName` (${fields.map { it.columnName }.joinToString(", ") { "`$it`" }})
-            """.trimIndent().replace("\n", " ")
-    }
-
-    val columnNames by lazy { fields.map { it.columnName } }
-
-    fun toBundle(): IndexBundle = IndexBundle(name, unique, fields.map { it.columnName },
-            createQuery(BundleUtil.TABLE_NAME_PLACEHOLDER))
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/InsertionMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/InsertionMethod.kt
deleted file mode 100644
index 19573cf..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/InsertionMethod.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.OnConflictStrategy
-import android.arch.persistence.room.ext.typeName
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.TypeMirror
-
-data class InsertionMethod(val element: ExecutableElement, val name: String,
-                           @OnConflictStrategy val onConflict: Int,
-                           val entities: Map<String, Entity>, val returnType: TypeMirror,
-                           val insertionType: Type?,
-                           val parameters: List<ShortcutQueryParameter>) {
-    fun insertMethodTypeFor(param: ShortcutQueryParameter): Type {
-        return if (insertionType == Type.INSERT_VOID || insertionType == null) {
-            Type.INSERT_VOID
-        } else if (!param.isMultiple) {
-            Type.INSERT_SINGLE_ID
-        } else {
-            insertionType
-        }
-    }
-
-    enum class Type(
-            // methodName matches EntityInsertionAdapter methods
-            val methodName: String, val returnTypeName: TypeName) {
-        INSERT_VOID("insert", TypeName.VOID), // return void
-        INSERT_SINGLE_ID("insertAndReturnId", TypeName.LONG), // return long
-        INSERT_ID_ARRAY("insertAndReturnIdsArray",
-                ArrayTypeName.of(TypeName.LONG)), // return long[]
-        INSERT_ID_ARRAY_BOX("insertAndReturnIdsArrayBox",
-                ArrayTypeName.of(TypeName.LONG.box())), // return Long[]
-        INSERT_ID_LIST("insertAndReturnIdsList", // return List<Long>
-                ParameterizedTypeName.get(List::class.typeName(), TypeName.LONG.box()))
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
deleted file mode 100644
index 34ec61c..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Pojo.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.processor.EntityProcessor
-import com.google.auto.common.MoreElements
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-
-/**
- * A class is turned into a Pojo if it is used in a query response.
- */
-open class Pojo(
-        val element: TypeElement,
-        val type: DeclaredType,
-        val fields: List<Field>,
-        val embeddedFields: List<EmbeddedField>,
-        val relations: List<Relation>,
-        val constructor: Constructor? = null) {
-    val typeName: TypeName by lazy { type.typeName() }
-
-    /**
-     * All table names that are somehow accessed by this Pojo.
-     * Might be via Embedded or Relation.
-     */
-    fun accessedTableNames(): List<String> {
-        val entityAnnotation = MoreElements.getAnnotationMirror(element,
-                android.arch.persistence.room.Entity::class.java).orNull()
-        return if (entityAnnotation != null) {
-            listOf(EntityProcessor.extractTableName(element, entityAnnotation))
-        } else {
-            embeddedFields.flatMap {
-                it.pojo.accessedTableNames()
-            } + relations.map {
-                it.entity.tableName
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PojoMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PojoMethod.kt
deleted file mode 100644
index 2a59ef8..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PojoMethod.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018 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.vo
-
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.ExecutableType
-
-/**
- * An executable element processed as member of a class (pojo or entity)
- */
-class PojoMethod(
-        val element: ExecutableElement,
-        val resolvedType: ExecutableType,
-        val name: String)
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PrimaryKey.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PrimaryKey.kt
deleted file mode 100644
index 1d76d40..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/PrimaryKey.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.migration.bundle.PrimaryKeyBundle
-import javax.lang.model.element.Element
-
-/**
- * Represents a PrimaryKey for an Entity.
- */
-data class PrimaryKey(val declaredIn: Element?, val fields: List<Field>,
-                      val autoGenerateId: Boolean) : HasSchemaIdentity {
-    companion object {
-        val MISSING = PrimaryKey(null, emptyList(), false)
-    }
-
-    val columnNames by lazy { fields.map { it.columnName } }
-
-    fun toHumanReadableString(): String {
-        return "PrimaryKey[" +
-                fields.joinToString(separator = ", ", transform = Field::getPath) + "]"
-    }
-
-    fun toBundle(): PrimaryKeyBundle = PrimaryKeyBundle(
-            autoGenerateId, fields.map { it.columnName })
-
-    override fun getIdKey(): String {
-        return "$autoGenerateId-${fields.map { it.columnName }}"
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt
deleted file mode 100644
index 8898d80..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryMethod.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.TypeMirror
-
-/**
- * A class that holds information about a QueryMethod.
- * It is self sufficient and must have all generics etc resolved once created.
- */
-data class QueryMethod(val element: ExecutableElement, val query: ParsedQuery, val name: String,
-                       val returnType: TypeMirror, val parameters: List<QueryParameter>,
-                       val inTransaction: Boolean,
-                       val queryResultBinder: QueryResultBinder) {
-    val sectionToParamMapping by lazy {
-        query.bindSections.map {
-            if (it.text.trim() == "?") {
-                Pair(it, parameters.firstOrNull())
-            } else if (it.text.startsWith(":")) {
-                val subName = it.text.substring(1)
-                Pair(it, parameters.firstOrNull {
-                    it.sqlName == subName
-                })
-            } else {
-                Pair(it, null)
-            }
-        }
-    }
-
-    val returnsValue by lazy {
-        returnType.typeName() != TypeName.VOID
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryParameter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryParameter.kt
deleted file mode 100644
index c33deac..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/QueryParameter.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.solver.query.parameter.QueryParameterAdapter
-import javax.lang.model.type.TypeMirror
-
-/**
- * Holds the parameter for a {@link QueryMethod}.
- */
-data class QueryParameter(
-        // this is name seen by java
-        val name: String,
-        // this is the name used in the query. Might be different for kotlin queries
-        val sqlName: String,
-        val type: TypeMirror,
-        val queryParamAdapter: QueryParameterAdapter?)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
deleted file mode 100644
index 5099906..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RawQueryMethod.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2018 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.vo
-
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.query.result.QueryResultBinder
-import com.squareup.javapoet.TypeName
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.TypeMirror
-
-/**
- * A class that holds information about a method annotated with RawQuery.
- * It is self sufficient and must have all generics etc resolved once created.
- */
-data class RawQueryMethod(
-        val element: ExecutableElement,
-        val name: String,
-        val returnType: TypeMirror,
-        val inTransaction: Boolean,
-        val observedTableNames: Set<String>,
-        val runtimeQueryParam: RuntimeQueryParameter?,
-        val queryResultBinder: QueryResultBinder) {
-    val returnsValue by lazy {
-        returnType.typeName() != TypeName.VOID
-    }
-
-    data class RuntimeQueryParameter(
-            val paramName: String,
-            val type: TypeName) {
-        fun isString() = CommonTypeNames.STRING == type
-        fun isSupportQuery() = SupportDbTypeNames.QUERY == type
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt
deleted file mode 100644
index 512770a..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Relation.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.typeName
-import javax.lang.model.type.TypeMirror
-
-/**
- * Value object created from processing a @Relation annotation.
- */
-class Relation(
-        val entity: Entity,
-        // return type. e..g. String in @Relation List<String>
-        val pojoType: TypeMirror,
-        // field in Pojo that holds these relations (e.g. List<Pet> pets)
-        val field: Field,
-        // the parent field referenced for matching
-        val parentField: Field,
-        // the field referenced for querying. does not need to be in the response but the query
-        // we generate always has it in the response.
-        val entityField: Field,
-        // the projection for the query
-        val projection: List<String>) {
-
-    val pojoTypeName by lazy { pojoType.typeName() }
-
-    fun createLoadAllSql(): String {
-        val resultFields = projection.toSet() + entityField.columnName
-        return createSelect(resultFields)
-    }
-
-    private fun createSelect(resultFields: Set<String>): String {
-        return "SELECT ${resultFields.joinToString(",") {"`$it`"}}" +
-                " FROM `${entity.tableName}`" +
-                " WHERE `${entityField.columnName}` IN (:args)"
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RelationCollector.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RelationCollector.kt
deleted file mode 100644
index add8fb1..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/RelationCollector.kt
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.parser.SqlParser
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER
-import android.arch.persistence.room.processor.ProcessorErrors.relationAffinityMismatch
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.solver.query.result.RowAdapter
-import android.arch.persistence.room.solver.query.result.SingleColumnRowAdapter
-import android.arch.persistence.room.verifier.DatabaseVerificaitonErrors
-import android.arch.persistence.room.writer.QueryWriter
-import android.arch.persistence.room.writer.RelationCollectorMethodWriter
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.CodeBlock
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import stripNonJava
-import java.util.ArrayList
-import java.util.HashSet
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-
-/**
- * Internal class that is used to manage fetching 1/N to N relationships.
- */
-data class RelationCollector(val relation: Relation,
-                             val affinity: SQLTypeAffinity,
-                             val mapTypeName: ParameterizedTypeName,
-                             val keyTypeName: TypeName,
-                             val collectionTypeName: ParameterizedTypeName,
-                             val queryWriter: QueryWriter,
-                             val rowAdapter: RowAdapter,
-                             val loadAllQuery: ParsedQuery) {
-    // set when writing the code generator in writeInitCode
-    lateinit var varName: String
-
-    fun writeInitCode(scope: CodeGenScope) {
-        val tmpVar = scope.getTmpVar(
-                "_collection${relation.field.getPath().stripNonJava().capitalize()}")
-        scope.builder().addStatement("final $T $L = new $T()", mapTypeName, tmpVar, mapTypeName)
-        varName = tmpVar
-    }
-
-    // called after reading each item to extract the key if it exists
-    fun writeReadParentKeyCode(cursorVarName: String, itemVar: String,
-                               fieldsWithIndices: List<FieldWithIndex>, scope: CodeGenScope) {
-        val indexVar = fieldsWithIndices.firstOrNull {
-            it.field === relation.parentField
-        }?.indexVar
-        scope.builder().apply {
-            readKey(
-                    cursorVarName = cursorVarName,
-                    indexVar = indexVar,
-                    scope = scope
-            ) { tmpVar ->
-                val tmpCollectionVar = scope.getTmpVar("_tmpCollection")
-                addStatement("$T $L = $L.get($L)", collectionTypeName, tmpCollectionVar,
-                        varName, tmpVar)
-                beginControlFlow("if($L == null)", tmpCollectionVar).apply {
-                    addStatement("$L = new $T()", tmpCollectionVar, collectionTypeName)
-                    addStatement("$L.put($L, $L)", varName, tmpVar, tmpCollectionVar)
-                }
-                endControlFlow()
-                // set it on the item
-                relation.field.setter.writeSet(itemVar, tmpCollectionVar, this)
-            }
-        }
-    }
-
-    fun writeCollectionCode(scope: CodeGenScope) {
-        val method = scope.writer
-                .getOrCreateMethod(RelationCollectorMethodWriter(this))
-        scope.builder().apply {
-            addStatement("$N($L)", method, varName)
-        }
-    }
-
-    fun readKey(cursorVarName: String, indexVar: String?, scope: CodeGenScope,
-                postRead: CodeBlock.Builder.(String) -> Unit) {
-        val cursorGetter = when (affinity) {
-            SQLTypeAffinity.INTEGER -> "getLong"
-            SQLTypeAffinity.REAL -> "getDouble"
-            SQLTypeAffinity.TEXT -> "getString"
-            SQLTypeAffinity.BLOB -> "getBlob"
-            else -> {
-                "getString"
-            }
-        }
-        scope.builder().apply {
-            beginControlFlow("if (!$L.isNull($L))", cursorVarName, indexVar).apply {
-                val tmpVar = scope.getTmpVar("_tmpKey")
-                addStatement("final $T $L = $L.$L($L)", keyTypeName,
-                        tmpVar, cursorVarName, cursorGetter, indexVar)
-                this.postRead(tmpVar)
-            }
-            endControlFlow()
-        }
-    }
-
-    companion object {
-        fun createCollectors(
-                baseContext: Context,
-                relations: List<Relation>
-        ): List<RelationCollector> {
-            return relations.map { relation ->
-                // decide on the affinity
-                val context = baseContext.fork(relation.field.element)
-                val parentAffinity = relation.parentField.cursorValueReader?.affinity()
-                val childAffinity = relation.entityField.cursorValueReader?.affinity()
-                val affinity = if (parentAffinity != null && parentAffinity == childAffinity) {
-                    parentAffinity
-                } else {
-                    context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
-                            relationAffinityMismatch(
-                                    parentColumn = relation.parentField.columnName,
-                                    childColumn = relation.entityField.columnName,
-                                    parentAffinity = parentAffinity,
-                                    childAffinity = childAffinity))
-                    SQLTypeAffinity.TEXT
-                }
-                val keyType = keyTypeFor(context, affinity)
-                val collectionTypeName = if (relation.field.typeName is ParameterizedTypeName) {
-                    val paramType = relation.field.typeName as ParameterizedTypeName
-                    if (paramType.rawType == CommonTypeNames.LIST) {
-                        ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
-                                relation.pojoTypeName)
-                    } else if (paramType.rawType == CommonTypeNames.SET) {
-                        ParameterizedTypeName.get(ClassName.get(HashSet::class.java),
-                                relation.pojoTypeName)
-                    } else {
-                        ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
-                                relation.pojoTypeName)
-                    }
-                } else {
-                    ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
-                            relation.pojoTypeName)
-                }
-
-                val canUseArrayMap = context.processingEnv.elementUtils
-                        .getTypeElement(AndroidTypeNames.ARRAY_MAP.toString()) != null
-                val mapClass = if (canUseArrayMap) {
-                    AndroidTypeNames.ARRAY_MAP
-                } else {
-                    ClassName.get(java.util.HashMap::class.java)
-                }
-                val tmpMapType = ParameterizedTypeName.get(mapClass, keyType, collectionTypeName)
-                val keyTypeMirror = keyTypeMirrorFor(context, affinity)
-                val set = context.processingEnv.elementUtils.getTypeElement("java.util.Set")
-                val keySet = context.processingEnv.typeUtils.getDeclaredType(set, keyTypeMirror)
-                val loadAllQuery = relation.createLoadAllSql()
-                val parsedQuery = SqlParser.parse(loadAllQuery)
-                context.checker.check(parsedQuery.errors.isEmpty(), relation.field.element,
-                        parsedQuery.errors.joinToString("\n"))
-                if (parsedQuery.errors.isEmpty()) {
-                    val resultInfo = context.databaseVerifier?.analyze(loadAllQuery)
-                    parsedQuery.resultInfo = resultInfo
-                    if (resultInfo?.error != null) {
-                        context.logger.e(relation.field.element,
-                                DatabaseVerificaitonErrors.cannotVerifyQuery(resultInfo.error))
-                    }
-                }
-                val resultInfo = parsedQuery.resultInfo
-
-                val queryParam = QueryParameter(
-                        name = RelationCollectorMethodWriter.KEY_SET_VARIABLE,
-                        sqlName = RelationCollectorMethodWriter.KEY_SET_VARIABLE,
-                        type = keySet,
-                        queryParamAdapter =
-                                context.typeAdapterStore.findQueryParameterAdapter(keySet))
-                val queryWriter = QueryWriter(
-                        parameters = listOf(queryParam),
-                        sectionToParamMapping = listOf(Pair(parsedQuery.bindSections.first(),
-                                queryParam)),
-                        query = parsedQuery
-                )
-
-                // row adapter that matches full response
-                fun getDefaultRowAdapter(): RowAdapter? {
-                    return context.typeAdapterStore.findRowAdapter(relation.pojoType, parsedQuery)
-                }
-                val rowAdapter = if (relation.projection.size == 1 && resultInfo != null &&
-                        (resultInfo.columns.size == 1 || resultInfo.columns.size == 2)) {
-                    // check for a column adapter first
-                    val cursorReader = context.typeAdapterStore.findCursorValueReader(
-                            relation.pojoType, resultInfo.columns.first().type)
-                    if (cursorReader == null) {
-                        getDefaultRowAdapter()
-                    } else {
-                        context.logger.d("Choosing cursor adapter for the return value since" +
-                                " the query returns only 1 or 2 columns and there is a cursor" +
-                                " adapter for the return type.")
-                        SingleColumnRowAdapter(cursorReader)
-                    }
-                } else {
-                    getDefaultRowAdapter()
-                }
-
-                if (rowAdapter == null) {
-                    context.logger.e(relation.field.element, CANNOT_FIND_QUERY_RESULT_ADAPTER)
-                    null
-                } else {
-                    RelationCollector(
-                            relation = relation,
-                            affinity = affinity,
-                            mapTypeName = tmpMapType,
-                            keyTypeName = keyType,
-                            collectionTypeName = collectionTypeName,
-                            queryWriter = queryWriter,
-                            rowAdapter = rowAdapter,
-                            loadAllQuery = parsedQuery
-                    )
-                }
-            }.filterNotNull()
-        }
-
-        private fun keyTypeMirrorFor(context: Context, affinity: SQLTypeAffinity): TypeMirror {
-            val types = context.processingEnv.typeUtils
-            val elements = context.processingEnv.elementUtils
-            return when (affinity) {
-                SQLTypeAffinity.INTEGER -> elements.getTypeElement("java.lang.Long").asType()
-                SQLTypeAffinity.REAL -> elements.getTypeElement("java.lang.Double").asType()
-                SQLTypeAffinity.TEXT -> context.COMMON_TYPES.STRING
-                SQLTypeAffinity.BLOB -> types.getArrayType(types.getPrimitiveType(TypeKind.BYTE))
-                else -> {
-                    context.COMMON_TYPES.STRING
-                }
-            }
-        }
-
-        private fun keyTypeFor(context: Context, affinity: SQLTypeAffinity): TypeName {
-            return when (affinity) {
-                SQLTypeAffinity.INTEGER -> TypeName.LONG.box()
-                SQLTypeAffinity.REAL -> TypeName.DOUBLE.box()
-                SQLTypeAffinity.TEXT -> TypeName.get(String::class.java)
-                SQLTypeAffinity.BLOB -> ArrayTypeName.of(TypeName.BYTE)
-                else -> {
-                    // no affinity select from type
-                    context.COMMON_TYPES.STRING.typeName()
-                }
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/SchemaIdentityKey.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/SchemaIdentityKey.kt
deleted file mode 100644
index 06c9ff5..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/SchemaIdentityKey.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 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.vo
-
-import org.apache.commons.codec.digest.DigestUtils
-import java.util.Locale
-
-interface HasSchemaIdentity {
-    fun getIdKey(): String
-}
-
-/**
- * A class that can be converted into a unique identifier for an object
- */
-class SchemaIdentityKey {
-    companion object {
-        private val SEPARATOR = "?:?"
-        private val ENGLISH_SORT = Comparator<String> { o1, o2 ->
-            o1.toLowerCase(Locale.ENGLISH).compareTo(o2.toLowerCase(Locale.ENGLISH))
-        }
-    }
-
-    private val sb = StringBuilder()
-    fun append(identity: HasSchemaIdentity) {
-        append(identity.getIdKey())
-    }
-
-    fun appendSorted(identities: List<HasSchemaIdentity>) {
-        identities.map { it.getIdKey() }.sortedWith(ENGLISH_SORT).forEach {
-            append(it)
-        }
-    }
-
-    fun hash() = DigestUtils.md5Hex(sb.toString())
-    fun append(identity: String) {
-        sb.append(identity).append(SEPARATOR)
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutMethod.kt
deleted file mode 100644
index 121f993..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutMethod.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import javax.lang.model.element.ExecutableElement
-
-/**
- * Base class for shortcut methods in @DAO.
- */
-abstract class ShortcutMethod(val element: ExecutableElement, val name: String,
-                              val entities: Map<String, Entity>, val returnCount: Boolean,
-                              val parameters: List<ShortcutQueryParameter>)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutQueryParameter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutQueryParameter.kt
deleted file mode 100644
index db8a0f5..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/ShortcutQueryParameter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import javax.lang.model.type.TypeMirror
-
-/**
- * Parameters used in DAO methods that are annotated with Insert, Delete, Update.
- */
-data class ShortcutQueryParameter(val name: String, val type: TypeMirror,
-                                  val entityType: TypeMirror?, val isMultiple: Boolean) {
-    /**
-     * Method name in entity insertion or update adapter.
-     */
-    fun handleMethodName(): String {
-        return if (isMultiple) {
-            "handleMultiple"
-        } else {
-            "handle"
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/TransactionMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/TransactionMethod.kt
deleted file mode 100644
index 10e1889..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/TransactionMethod.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import javax.lang.model.element.ExecutableElement
-
-class TransactionMethod(val element: ExecutableElement, val name: String, val callType: CallType) {
-    enum class CallType {
-        CONCRETE, DEFAULT_JAVA8, DEFAULT_KOTLIN
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/UpdateMethod.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/UpdateMethod.kt
deleted file mode 100644
index ed8eb8b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/UpdateMethod.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.OnConflictStrategy
-import javax.lang.model.element.ExecutableElement
-
-class UpdateMethod(element: ExecutableElement, name: String,
-                   entities: Map<String, Entity>, returnCount: Boolean,
-                   parameters: List<ShortcutQueryParameter>,
-                   @OnConflictStrategy val onConflictStrategy: Int) : ShortcutMethod(
-        element, name, entities, returnCount, parameters)
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt
deleted file mode 100644
index dd154c4..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/vo/Warning.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-/**
- * Internal representation of supported warnings
- */
-enum class Warning(val publicKey: String) {
-    ALL("ALL"),
-    CURSOR_MISMATCH("ROOM_CURSOR_MISMATCH"),
-    MISSING_JAVA_TMP_DIR("ROOM_MISSING_JAVA_TMP_DIR"),
-    CANNOT_CREATE_VERIFICATION_DATABASE("ROOM_CANNOT_CREATE_VERIFICATION_DATABASE"),
-    PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED("ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED"),
-    INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED("ROOM_EMBEDDED_INDEX_IS_DROPPED"),
-    INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED("ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED"),
-    INDEX_FROM_PARENT_IS_DROPPED("ROOM_PARENT_INDEX_IS_DROPPED"),
-    INDEX_FROM_PARENT_FIELD_IS_DROPPED("ROOM_PARENT_FIELD_INDEX_IS_DROPPED"),
-    RELATION_TYPE_MISMATCH("ROOM_RELATION_TYPE_MISMATCH"),
-    MISSING_SCHEMA_LOCATION("ROOM_MISSING_SCHEMA_LOCATION"),
-    MISSING_INDEX_ON_FOREIGN_KEY_CHILD("ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX"),
-    RELATION_QUERY_WITHOUT_TRANSACTION("ROOM_RELATION_QUERY_WITHOUT_TRANSACTION"),
-    DEFAULT_CONSTRUCTOR("ROOM_DEFAULT_CONSTRUCTOR");
-
-    companion object {
-        val PUBLIC_KEY_MAP = Warning.values().associateBy { it.publicKey }
-        fun fromPublicKey(publicKey: String): Warning? {
-            return PUBLIC_KEY_MAP[publicKey.toUpperCase()]
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/ClassWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/ClassWriter.kt
deleted file mode 100644
index d4dd116..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/ClassWriter.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.RoomProcessor
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope.Companion.CLASS_PROPERTY_PREFIX
-import com.squareup.javapoet.AnnotationSpec
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.JavaFile
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.annotation.processing.ProcessingEnvironment
-
-/**
- * Base class for all writers that can produce a class.
- */
-abstract class ClassWriter(private val className: ClassName) {
-    private val sharedFieldSpecs = mutableMapOf<String, FieldSpec>()
-    private val sharedMethodSpecs = mutableMapOf<String, MethodSpec>()
-    private val sharedFieldNames = mutableSetOf<String>()
-    private val sharedMethodNames = mutableSetOf<String>()
-
-    abstract fun createTypeSpecBuilder(): TypeSpec.Builder
-
-    fun write(processingEnv: ProcessingEnvironment) {
-        val builder = createTypeSpecBuilder()
-        sharedFieldSpecs.values.forEach { builder.addField(it) }
-        sharedMethodSpecs.values.forEach { builder.addMethod(it) }
-        addGeneratedAnnotationIfAvailable(builder, processingEnv)
-        addSuppressUnchecked(builder)
-        JavaFile.builder(className.packageName(), builder.build())
-                .build()
-                .writeTo(processingEnv.filer)
-    }
-
-    private fun addSuppressUnchecked(builder: TypeSpec.Builder) {
-        val suppressSpec = AnnotationSpec.builder(SuppressWarnings::class.typeName()).addMember(
-                "value",
-                S,
-                "unchecked"
-        ).build()
-        builder.addAnnotation(suppressSpec)
-    }
-
-    private fun addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder: TypeSpec.Builder,
-                                                  processingEnv: ProcessingEnvironment) {
-        val generatedAnnotationAvailable = processingEnv
-                .elementUtils
-                .getTypeElement(GENERATED_PACKAGE + "." + GENERATED_NAME) != null
-        if (generatedAnnotationAvailable) {
-            val className = ClassName.get(GENERATED_PACKAGE, GENERATED_NAME)
-            val generatedAnnotationSpec =
-                    AnnotationSpec.builder(className).addMember(
-                            "value",
-                            S,
-                            RoomProcessor::class.java.canonicalName).build()
-            adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
-        }
-    }
-
-    private fun makeUnique(set: MutableSet<String>, value: String): String {
-        if (!value.startsWith(CLASS_PROPERTY_PREFIX)) {
-            return makeUnique(set, "$CLASS_PROPERTY_PREFIX$value")
-        }
-        if (set.add(value)) {
-            return value
-        }
-        var index = 1
-        while (true) {
-            if (set.add("${value}_$index")) {
-                return "${value}_$index"
-            }
-            index++
-        }
-    }
-
-    fun getOrCreateField(sharedField: SharedFieldSpec): FieldSpec {
-        return sharedFieldSpecs.getOrPut(sharedField.getUniqueKey(), {
-            sharedField.build(this, makeUnique(sharedFieldNames, sharedField.baseName))
-        })
-    }
-
-    fun getOrCreateMethod(sharedMethod: SharedMethodSpec): MethodSpec {
-        return sharedMethodSpecs.getOrPut(sharedMethod.getUniqueKey(), {
-            sharedMethod.build(this, makeUnique(sharedMethodNames, sharedMethod.baseName))
-        })
-    }
-
-    abstract class SharedFieldSpec(val baseName: String, val type: TypeName) {
-
-        abstract fun getUniqueKey(): String
-
-        abstract fun prepare(writer: ClassWriter, builder: FieldSpec.Builder)
-
-        fun build(classWriter: ClassWriter, name: String): FieldSpec {
-            val builder = FieldSpec.builder(type, name)
-            prepare(classWriter, builder)
-            return builder.build()
-        }
-    }
-
-    abstract class SharedMethodSpec(val baseName: String) {
-
-        abstract fun getUniqueKey(): String
-        abstract fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder)
-
-        fun build(writer: ClassWriter, name: String): MethodSpec {
-            val builder = MethodSpec.methodBuilder(name)
-            prepare(name, writer, builder)
-            return builder.build()
-        }
-    }
-
-    companion object {
-        private const val GENERATED_PACKAGE = "javax.annotation"
-        private const val GENERATED_NAME = "Generated"
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt
deleted file mode 100644
index f0d2771..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DaoWriter.kt
+++ /dev/null
@@ -1,605 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.QueryType
-import android.arch.persistence.room.processor.OnConflictProcessor
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.Dao
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.InsertionMethod
-import android.arch.persistence.room.vo.QueryMethod
-import android.arch.persistence.room.vo.RawQueryMethod
-import android.arch.persistence.room.vo.ShortcutMethod
-import android.arch.persistence.room.vo.TransactionMethod
-import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.CodeBlock
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import org.jetbrains.kotlin.load.java.JvmAbi
-import stripNonJava
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.Modifier.FINAL
-import javax.lang.model.element.Modifier.PRIVATE
-import javax.lang.model.element.Modifier.PUBLIC
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeKind
-
-/**
- * Creates the implementation for a class annotated with Dao.
- */
-class DaoWriter(val dao: Dao, val processingEnv: ProcessingEnvironment)
-    : ClassWriter(dao.typeName) {
-    private val declaredDao = MoreTypes.asDeclared(dao.element.asType())
-
-    companion object {
-        // TODO nothing prevents this from conflicting, we should fix.
-        val dbField: FieldSpec = FieldSpec
-                .builder(RoomTypeNames.ROOM_DB, "__db", PRIVATE, FINAL)
-                .build()
-
-        private fun typeNameToFieldName(typeName: TypeName?): String {
-            if (typeName is ClassName) {
-                return typeName.simpleName()
-            } else {
-                return typeName.toString().replace('.', '_').stripNonJava()
-            }
-        }
-    }
-
-    override fun createTypeSpecBuilder(): TypeSpec.Builder {
-        val builder = TypeSpec.classBuilder(dao.implTypeName)
-        /**
-         * if delete / update query method wants to return modified rows, we need prepared query.
-         * in that case, if args are dynamic, we cannot re-use the query, if not, we should re-use
-         * it. this requires more work but creates good performance.
-         */
-        val groupedDeleteUpdate = dao.queryMethods
-                .filter { it.query.type == QueryType.DELETE || it.query.type == QueryType.UPDATE }
-                .groupBy { it.parameters.any { it.queryParamAdapter?.isMultiple ?: true } }
-        // delete queries that can be prepared ahead of time
-        val preparedDeleteOrUpdateQueries = groupedDeleteUpdate[false] ?: emptyList()
-        // delete queries that must be rebuild every single time
-        val oneOffDeleteOrUpdateQueries = groupedDeleteUpdate[true] ?: emptyList()
-        val shortcutMethods = createInsertionMethods() +
-                createDeletionMethods() + createUpdateMethods() + createTransactionMethods() +
-                createPreparedDeleteOrUpdateQueries(preparedDeleteOrUpdateQueries)
-
-        builder.apply {
-            addModifiers(PUBLIC)
-            if (dao.element.kind == ElementKind.INTERFACE) {
-                addSuperinterface(dao.typeName)
-            } else {
-                superclass(dao.typeName)
-            }
-            addField(dbField)
-            val dbParam = ParameterSpec
-                    .builder(dao.constructorParamType ?: dbField.type, dbField.name).build()
-
-            addMethod(createConstructor(dbParam, shortcutMethods, dao.constructorParamType != null))
-
-            shortcutMethods.forEach {
-                addMethod(it.methodImpl)
-            }
-
-            dao.queryMethods.filter { it.query.type == QueryType.SELECT }.forEach { method ->
-                addMethod(createSelectMethod(method))
-            }
-            oneOffDeleteOrUpdateQueries.forEach {
-                addMethod(createDeleteOrUpdateQueryMethod(it))
-            }
-            dao.rawQueryMethods.forEach {
-                addMethod(createRawQueryMethod(it))
-            }
-        }
-        return builder
-    }
-
-    private fun createPreparedDeleteOrUpdateQueries(
-            preparedDeleteQueries: List<QueryMethod>): List<PreparedStmtQuery> {
-        return preparedDeleteQueries.map { method ->
-            val fieldSpec = getOrCreateField(PreparedStatementField(method))
-            val queryWriter = QueryWriter(method)
-            val fieldImpl = PreparedStatementWriter(queryWriter)
-                    .createAnonymous(this@DaoWriter, dbField)
-            val methodBody = createPreparedDeleteQueryMethodBody(method, fieldSpec, queryWriter)
-            PreparedStmtQuery(mapOf(PreparedStmtQuery.NO_PARAM_FIELD
-                    to (fieldSpec to fieldImpl)), methodBody)
-        }
-    }
-
-    private fun createPreparedDeleteQueryMethodBody(
-            method: QueryMethod,
-            preparedStmtField: FieldSpec,
-            queryWriter: QueryWriter
-    ): MethodSpec {
-        val scope = CodeGenScope(this)
-        val methodBuilder = overrideWithoutAnnotations(method.element, declaredDao).apply {
-            val stmtName = scope.getTmpVar("_stmt")
-            addStatement("final $T $L = $N.acquire()",
-                    SupportDbTypeNames.SQLITE_STMT, stmtName, preparedStmtField)
-            addStatement("$N.beginTransaction()", dbField)
-            beginControlFlow("try").apply {
-                val bindScope = scope.fork()
-                queryWriter.bindArgs(stmtName, emptyList(), bindScope)
-                addCode(bindScope.builder().build())
-                if (method.returnsValue) {
-                    val resultVar = scope.getTmpVar("_result")
-                    addStatement("final $L $L = $L.executeUpdateDelete()",
-                            method.returnType.typeName(), resultVar, stmtName)
-                    addStatement("$N.setTransactionSuccessful()", dbField)
-                    addStatement("return $L", resultVar)
-                } else {
-                    addStatement("$L.executeUpdateDelete()", stmtName)
-                    addStatement("$N.setTransactionSuccessful()", dbField)
-                }
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$N.endTransaction()", dbField)
-                addStatement("$N.release($L)", preparedStmtField, stmtName)
-            }
-            endControlFlow()
-        }
-        return methodBuilder.build()
-    }
-
-    private fun createTransactionMethods(): List<PreparedStmtQuery> {
-        return dao.transactionMethods.map {
-            PreparedStmtQuery(emptyMap(), createTransactionMethodBody(it))
-        }
-    }
-
-    private fun createTransactionMethodBody(method: TransactionMethod): MethodSpec {
-        val scope = CodeGenScope(this)
-        val methodBuilder = overrideWithoutAnnotations(method.element, declaredDao).apply {
-            addStatement("$N.beginTransaction()", dbField)
-            beginControlFlow("try").apply {
-                val returnsValue = method.element.returnType.kind != TypeKind.VOID
-                val resultVar = if (returnsValue) {
-                    scope.getTmpVar("_result")
-                } else {
-                    null
-                }
-                addDelegateToSuperStatement(method.element, method.callType, resultVar)
-                addStatement("$N.setTransactionSuccessful()", dbField)
-                if (returnsValue) {
-                    addStatement("return $N", resultVar)
-                }
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$N.endTransaction()", dbField)
-            }
-            endControlFlow()
-        }
-        return methodBuilder.build()
-    }
-
-    private fun MethodSpec.Builder.addDelegateToSuperStatement(
-            element: ExecutableElement,
-            callType: TransactionMethod.CallType,
-            result: String?) {
-        val params: MutableList<Any> = mutableListOf()
-        val format = buildString {
-            if (result != null) {
-                append("$T $L = ")
-                params.add(element.returnType)
-                params.add(result)
-            }
-            when (callType) {
-                TransactionMethod.CallType.CONCRETE -> {
-                    append("super.$N(")
-                    params.add(element.simpleName)
-                }
-                TransactionMethod.CallType.DEFAULT_JAVA8 -> {
-                    append("$N.super.$N(")
-                    params.add(element.enclosingElement.simpleName)
-                    params.add(element.simpleName)
-                }
-                TransactionMethod.CallType.DEFAULT_KOTLIN -> {
-                    append("$N.$N.$N(this, ")
-                    params.add(element.enclosingElement.simpleName)
-                    params.add(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)
-                    params.add(element.simpleName)
-                }
-            }
-            var first = true
-            element.parameters.forEach {
-                if (first) {
-                    first = false
-                } else {
-                    append(", ")
-                }
-                append(L)
-                params.add(it.simpleName)
-            }
-            append(")")
-        }
-        addStatement(format, *params.toTypedArray())
-    }
-
-    private fun createConstructor(
-            dbParam: ParameterSpec,
-            shortcutMethods: List<PreparedStmtQuery>,
-            callSuper: Boolean): MethodSpec {
-        return MethodSpec.constructorBuilder().apply {
-            addParameter(dbParam)
-            addModifiers(PUBLIC)
-            if (callSuper) {
-                addStatement("super($N)", dbParam)
-            }
-            addStatement("this.$N = $N", dbField, dbParam)
-            shortcutMethods.filterNot {
-                it.fields.isEmpty()
-            }.map {
-                it.fields.values
-            }.flatten().groupBy {
-                it.first.name
-            }.map {
-                it.value.first()
-            }.forEach {
-                addStatement("this.$N = $L", it.first, it.second)
-            }
-        }.build()
-    }
-
-    private fun createSelectMethod(method: QueryMethod): MethodSpec {
-        return overrideWithoutAnnotations(method.element, declaredDao).apply {
-            addCode(createQueryMethodBody(method))
-        }.build()
-    }
-
-    private fun createRawQueryMethod(method: RawQueryMethod): MethodSpec {
-        return overrideWithoutAnnotations(method.element, declaredDao).apply {
-            val scope = CodeGenScope(this@DaoWriter)
-            val roomSQLiteQueryVar: String
-            val queryParam = method.runtimeQueryParam
-            val shouldReleaseQuery: Boolean
-
-            when {
-                queryParam?.isString() == true -> {
-                    roomSQLiteQueryVar = scope.getTmpVar("_statement")
-                    shouldReleaseQuery = true
-                    addStatement("$T $L = $T.acquire($L, 0)",
-                            RoomTypeNames.ROOM_SQL_QUERY,
-                            roomSQLiteQueryVar,
-                            RoomTypeNames.ROOM_SQL_QUERY,
-                            queryParam.paramName)
-                }
-                queryParam?.isSupportQuery() == true -> {
-                    shouldReleaseQuery = false
-                    roomSQLiteQueryVar = scope.getTmpVar("_internalQuery")
-                    // move it to a final variable so that the generated code can use it inside
-                    // callback blocks in java 7
-                    addStatement("final $T $L = $N",
-                            queryParam.type,
-                            roomSQLiteQueryVar,
-                            queryParam.paramName)
-                }
-                else -> {
-                    // try to generate compiling code. we would've already reported this error
-                    roomSQLiteQueryVar = scope.getTmpVar("_statement")
-                    shouldReleaseQuery = false
-                    addStatement("$T $L = $T.acquire($L, 0)",
-                            RoomTypeNames.ROOM_SQL_QUERY,
-                            roomSQLiteQueryVar,
-                            RoomTypeNames.ROOM_SQL_QUERY,
-                            "missing query parameter")
-                }
-            }
-            if (method.returnsValue) {
-                // don't generate code because it will create 1 more error. The original error is
-                // already reported by the processor.
-                method.queryResultBinder.convertAndReturn(
-                        roomSQLiteQueryVar = roomSQLiteQueryVar,
-                        canReleaseQuery = shouldReleaseQuery,
-                        dbField = dbField,
-                        inTransaction = method.inTransaction,
-                        scope = scope)
-            }
-            addCode(scope.builder().build())
-        }.build()
-    }
-
-    private fun createDeleteOrUpdateQueryMethod(method: QueryMethod): MethodSpec {
-        return overrideWithoutAnnotations(method.element, declaredDao).apply {
-            addCode(createDeleteOrUpdateQueryMethodBody(method))
-        }.build()
-    }
-
-    /**
-     * Groups all insertion methods based on the insert statement they will use then creates all
-     * field specs, EntityInsertionAdapterWriter and actual insert methods.
-     */
-    private fun createInsertionMethods(): List<PreparedStmtQuery> {
-        return dao.insertionMethods
-                .map { insertionMethod ->
-                    val onConflict = OnConflictProcessor.onConflictText(insertionMethod.onConflict)
-                    val entities = insertionMethod.entities
-
-                    val fields = entities.mapValues {
-                        val spec = getOrCreateField(InsertionMethodField(it.value, onConflict))
-                        val impl = EntityInsertionAdapterWriter(it.value, onConflict)
-                                .createAnonymous(this@DaoWriter, dbField.name)
-                        spec to impl
-                    }
-                    val methodImpl = overrideWithoutAnnotations(insertionMethod.element,
-                            declaredDao).apply {
-                        addCode(createInsertionMethodBody(insertionMethod, fields))
-                    }.build()
-                    PreparedStmtQuery(fields, methodImpl)
-                }.filterNotNull()
-    }
-
-    private fun createInsertionMethodBody(
-            method: InsertionMethod,
-            insertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>
-    ): CodeBlock {
-        val insertionType = method.insertionType
-        if (insertionAdapters.isEmpty() || insertionType == null) {
-            return CodeBlock.builder().build()
-        }
-        val scope = CodeGenScope(this)
-
-        return scope.builder().apply {
-            // TODO assert thread
-            // TODO collect results
-            addStatement("$N.beginTransaction()", dbField)
-            val needsReturnType = insertionType != InsertionMethod.Type.INSERT_VOID
-            val resultVar = if (needsReturnType) {
-                scope.getTmpVar("_result")
-            } else {
-                null
-            }
-
-            beginControlFlow("try").apply {
-                method.parameters.forEach { param ->
-                    val insertionAdapter = insertionAdapters[param.name]?.first
-                    if (needsReturnType) {
-                        // if it has more than 1 parameter, we would've already printed the error
-                        // so we don't care about re-declaring the variable here
-                        addStatement("$T $L = $N.$L($L)",
-                                insertionType.returnTypeName, resultVar,
-                                insertionAdapter, insertionType.methodName,
-                                param.name)
-                    } else {
-                        addStatement("$N.$L($L)", insertionAdapter, insertionType.methodName,
-                                param.name)
-                    }
-                }
-                addStatement("$N.setTransactionSuccessful()", dbField)
-                if (needsReturnType) {
-                    addStatement("return $L", resultVar)
-                }
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$N.endTransaction()", dbField)
-            }
-            endControlFlow()
-        }.build()
-    }
-
-    /**
-     * Creates EntityUpdateAdapter for each deletion method.
-     */
-    private fun createDeletionMethods(): List<PreparedStmtQuery> {
-        return createShortcutMethods(dao.deletionMethods, "deletion", { _, entity ->
-            EntityDeletionAdapterWriter(entity)
-                    .createAnonymous(this@DaoWriter, dbField.name)
-        })
-    }
-
-    /**
-     * Creates EntityUpdateAdapter for each @Update method.
-     */
-    private fun createUpdateMethods(): List<PreparedStmtQuery> {
-        return createShortcutMethods(dao.updateMethods, "update", { update, entity ->
-            val onConflict = OnConflictProcessor.onConflictText(update.onConflictStrategy)
-            EntityUpdateAdapterWriter(entity, onConflict)
-                    .createAnonymous(this@DaoWriter, dbField.name)
-        })
-    }
-
-    private fun <T : ShortcutMethod> createShortcutMethods(
-            methods: List<T>, methodPrefix: String,
-            implCallback: (T, Entity) -> TypeSpec
-    ): List<PreparedStmtQuery> {
-        return methods.map { method ->
-            val entities = method.entities
-
-            if (entities.isEmpty()) {
-                null
-            } else {
-                val fields = entities.mapValues {
-                    val spec = getOrCreateField(DeleteOrUpdateAdapterField(it.value, methodPrefix))
-                    val impl = implCallback(method, it.value)
-                    spec to impl
-                }
-                val methodSpec = overrideWithoutAnnotations(method.element, declaredDao).apply {
-                    addCode(createDeleteOrUpdateMethodBody(method, fields))
-                }.build()
-                PreparedStmtQuery(fields, methodSpec)
-            }
-        }.filterNotNull()
-    }
-
-    private fun createDeleteOrUpdateMethodBody(
-            method: ShortcutMethod,
-            adapters: Map<String, Pair<FieldSpec, TypeSpec>>
-    ): CodeBlock {
-        if (adapters.isEmpty()) {
-            return CodeBlock.builder().build()
-        }
-        val scope = CodeGenScope(this)
-        val resultVar = if (method.returnCount) {
-            scope.getTmpVar("_total")
-        } else {
-            null
-        }
-        return scope.builder().apply {
-            if (resultVar != null) {
-                addStatement("$T $L = 0", TypeName.INT, resultVar)
-            }
-            addStatement("$N.beginTransaction()", dbField)
-            beginControlFlow("try").apply {
-                method.parameters.forEach { param ->
-                    val adapter = adapters[param.name]?.first
-                    addStatement("$L$N.$L($L)",
-                            if (resultVar == null) "" else "$resultVar +=",
-                            adapter, param.handleMethodName(), param.name)
-                }
-                addStatement("$N.setTransactionSuccessful()", dbField)
-                if (resultVar != null) {
-                    addStatement("return $L", resultVar)
-                }
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$N.endTransaction()", dbField)
-            }
-            endControlFlow()
-        }.build()
-    }
-
-    /**
-     * @Query with delete action
-     */
-    private fun createDeleteOrUpdateQueryMethodBody(method: QueryMethod): CodeBlock {
-        val queryWriter = QueryWriter(method)
-        val scope = CodeGenScope(this)
-        val sqlVar = scope.getTmpVar("_sql")
-        val stmtVar = scope.getTmpVar("_stmt")
-        val listSizeArgs = queryWriter.prepareQuery(sqlVar, scope)
-        scope.builder().apply {
-            addStatement("$T $L = $N.compileStatement($L)",
-                    SupportDbTypeNames.SQLITE_STMT, stmtVar, dbField, sqlVar)
-            queryWriter.bindArgs(stmtVar, listSizeArgs, scope)
-            addStatement("$N.beginTransaction()", dbField)
-            beginControlFlow("try").apply {
-                if (method.returnsValue) {
-                    val resultVar = scope.getTmpVar("_result")
-                    addStatement("final $L $L = $L.executeUpdateDelete()",
-                            method.returnType.typeName(), resultVar, stmtVar)
-                    addStatement("$N.setTransactionSuccessful()", dbField)
-                    addStatement("return $L", resultVar)
-                } else {
-                    addStatement("$L.executeUpdateDelete()", stmtVar)
-                    addStatement("$N.setTransactionSuccessful()", dbField)
-                }
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$N.endTransaction()", dbField)
-            }
-            endControlFlow()
-        }
-        return scope.builder().build()
-    }
-
-    private fun createQueryMethodBody(method: QueryMethod): CodeBlock {
-        val queryWriter = QueryWriter(method)
-        val scope = CodeGenScope(this)
-        val sqlVar = scope.getTmpVar("_sql")
-        val roomSQLiteQueryVar = scope.getTmpVar("_statement")
-        queryWriter.prepareReadAndBind(sqlVar, roomSQLiteQueryVar, scope)
-        method.queryResultBinder.convertAndReturn(
-                roomSQLiteQueryVar = roomSQLiteQueryVar,
-                canReleaseQuery = true,
-                dbField = dbField,
-                inTransaction = method.inTransaction,
-                scope = scope)
-        return scope.builder().build()
-    }
-
-    private fun overrideWithoutAnnotations(
-            elm: ExecutableElement,
-            owner: DeclaredType): MethodSpec.Builder {
-        val baseSpec = MethodSpec.overriding(elm, owner, processingEnv.typeUtils).build()
-        return MethodSpec.methodBuilder(baseSpec.name).apply {
-            addAnnotation(Override::class.java)
-            addModifiers(baseSpec.modifiers)
-            addParameters(baseSpec.parameters)
-            varargs(baseSpec.varargs)
-            returns(baseSpec.returnType)
-        }
-    }
-
-    /**
-     * Represents a query statement prepared in Dao implementation.
-     *
-     * @param fields This map holds all the member fields necessary for this query. The key is the
-     * corresponding parameter name in the defining query method. The value is a pair from the field
-     * declaration to definition.
-     * @param methodImpl The body of the query method implementation.
-     */
-    data class PreparedStmtQuery(
-            val fields: Map<String, Pair<FieldSpec, TypeSpec>>,
-            val methodImpl: MethodSpec) {
-        companion object {
-            // The key to be used in `fields` where the method requires a field that is not
-            // associated with any of its parameters
-            const val NO_PARAM_FIELD = "-"
-        }
-    }
-
-    private class InsertionMethodField(val entity: Entity, val onConflictText: String)
-        : SharedFieldSpec(
-            "insertionAdapterOf${Companion.typeNameToFieldName(entity.typeName)}",
-            RoomTypeNames.INSERTION_ADAPTER) {
-
-        override fun getUniqueKey(): String {
-            return "${entity.typeName} $onConflictText"
-        }
-
-        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
-            builder.addModifiers(FINAL, PRIVATE)
-        }
-    }
-
-    class DeleteOrUpdateAdapterField(val entity: Entity, val methodPrefix: String)
-        : SharedFieldSpec(
-            "${methodPrefix}AdapterOf${Companion.typeNameToFieldName(entity.typeName)}",
-            RoomTypeNames.DELETE_OR_UPDATE_ADAPTER) {
-        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
-            builder.addModifiers(PRIVATE, FINAL)
-        }
-
-        override fun getUniqueKey(): String {
-            return entity.typeName.toString() + methodPrefix
-        }
-    }
-
-    class PreparedStatementField(val method: QueryMethod) : SharedFieldSpec(
-            "preparedStmtOf${method.name.capitalize()}", RoomTypeNames.SHARED_SQLITE_STMT) {
-        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
-            builder.addModifiers(PRIVATE, FINAL)
-        }
-
-        override fun getUniqueKey(): String {
-            return method.query.original
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt
deleted file mode 100644
index 7e58430..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.DaoMethod
-import android.arch.persistence.room.vo.Database
-import com.google.auto.common.MoreElements
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import stripNonJava
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.Modifier.PRIVATE
-import javax.lang.model.element.Modifier.PROTECTED
-import javax.lang.model.element.Modifier.PUBLIC
-import javax.lang.model.element.Modifier.VOLATILE
-
-/**
- * Writes implementation of classes that were annotated with @Database.
- */
-class DatabaseWriter(val database: Database) : ClassWriter(database.implTypeName) {
-    override fun createTypeSpecBuilder(): TypeSpec.Builder {
-        val builder = TypeSpec.classBuilder(database.implTypeName)
-        builder.apply {
-            addModifiers(PUBLIC)
-            superclass(database.typeName)
-            addMethod(createCreateOpenHelper())
-            addMethod(createCreateInvalidationTracker())
-            addMethod(createClearAllTables())
-        }
-        addDaoImpls(builder)
-        return builder
-    }
-
-    private fun createClearAllTables(): MethodSpec {
-        val scope = CodeGenScope(this)
-        return MethodSpec.methodBuilder("clearAllTables").apply {
-            val dbVar = scope.getTmpVar("_db")
-            addStatement("final $T $L = super.getOpenHelper().getWritableDatabase()",
-                    SupportDbTypeNames.DB, dbVar)
-            val deferVar = scope.getTmpVar("_supportsDeferForeignKeys")
-            if (database.enableForeignKeys) {
-                addStatement("boolean $L = $L.VERSION.SDK_INT >= $L.VERSION_CODES.LOLLIPOP",
-                        deferVar, AndroidTypeNames.BUILD, AndroidTypeNames.BUILD)
-            }
-            addAnnotation(Override::class.java)
-            addModifiers(PUBLIC)
-            returns(TypeName.VOID)
-            beginControlFlow("try").apply {
-                if (database.enableForeignKeys) {
-                    beginControlFlow("if (!$L)", deferVar).apply {
-                        addStatement("$L.execSQL($S)", dbVar, "PRAGMA foreign_keys = FALSE")
-                    }
-                    endControlFlow()
-                }
-                addStatement("super.beginTransaction()")
-                if (database.enableForeignKeys) {
-                    beginControlFlow("if ($L)", deferVar).apply {
-                        addStatement("$L.execSQL($S)", dbVar, "PRAGMA defer_foreign_keys = TRUE")
-                    }
-                    endControlFlow()
-                }
-                database.entities.sortedWith(EntityDeleteComparator()).forEach {
-                    addStatement("$L.execSQL($S)", dbVar, "DELETE FROM `${it.tableName}`")
-                }
-                addStatement("super.setTransactionSuccessful()")
-            }
-            nextControlFlow("finally").apply {
-                addStatement("super.endTransaction()")
-                if (database.enableForeignKeys) {
-                    beginControlFlow("if (!$L)", deferVar).apply {
-                        addStatement("$L.execSQL($S)", dbVar, "PRAGMA foreign_keys = TRUE")
-                    }
-                    endControlFlow()
-                }
-            }
-            endControlFlow()
-        }.build()
-    }
-
-    private fun createCreateInvalidationTracker(): MethodSpec {
-        return MethodSpec.methodBuilder("createInvalidationTracker").apply {
-            addAnnotation(Override::class.java)
-            addModifiers(PROTECTED)
-            returns(RoomTypeNames.INVALIDATION_TRACKER)
-            val tableNames = database.entities.joinToString(",") {
-                "\"${it.tableName}\""
-            }
-            addStatement("return new $T(this, $L)", RoomTypeNames.INVALIDATION_TRACKER, tableNames)
-        }.build()
-    }
-
-    private fun addDaoImpls(builder: TypeSpec.Builder) {
-        val scope = CodeGenScope(this)
-        builder.apply {
-            database.daoMethods.forEach { method ->
-                val name = method.dao.typeName.simpleName().decapitalize().stripNonJava()
-                val fieldName = scope.getTmpVar("_$name")
-                val field = FieldSpec.builder(method.dao.typeName, fieldName,
-                        PRIVATE, VOLATILE).build()
-                addField(field)
-                addMethod(createDaoGetter(field, method))
-            }
-        }
-    }
-
-    private fun createDaoGetter(field: FieldSpec, method: DaoMethod): MethodSpec {
-        return MethodSpec.overriding(MoreElements.asExecutable(method.element)).apply {
-            beginControlFlow("if ($N != null)", field).apply {
-                addStatement("return $N", field)
-            }
-            nextControlFlow("else").apply {
-                beginControlFlow("synchronized(this)").apply {
-                    beginControlFlow("if($N == null)", field).apply {
-                        addStatement("$N = new $T(this)", field, method.dao.implTypeName)
-                    }
-                    endControlFlow()
-                    addStatement("return $N", field)
-                }
-                endControlFlow()
-            }
-            endControlFlow()
-        }.build()
-    }
-
-    private fun createCreateOpenHelper(): MethodSpec {
-        val scope = CodeGenScope(this)
-        return MethodSpec.methodBuilder("createOpenHelper").apply {
-            addModifiers(Modifier.PROTECTED)
-            addAnnotation(Override::class.java)
-            returns(SupportDbTypeNames.SQLITE_OPEN_HELPER)
-
-            val configParam = ParameterSpec.builder(RoomTypeNames.ROOM_DB_CONFIG,
-                    "configuration").build()
-            addParameter(configParam)
-
-            val openHelperVar = scope.getTmpVar("_helper")
-            val openHelperCode = scope.fork()
-            SQLiteOpenHelperWriter(database)
-                    .write(openHelperVar, configParam, openHelperCode)
-            addCode(openHelperCode.builder().build())
-            addStatement("return $L", openHelperVar)
-        }.build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriter.kt
deleted file mode 100644
index b9d6aec..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriter.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.EmbeddedField
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.FieldWithIndex
-import com.squareup.javapoet.CodeBlock
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeName
-import stripNonJava
-import javax.lang.model.element.Modifier.PRIVATE
-
-class EntityCursorConverterWriter(val entity: Entity) : ClassWriter.SharedMethodSpec(
-        "entityCursorConverter_${entity.typeName.toString().stripNonJava()}") {
-    override fun getUniqueKey(): String {
-        return "generic_entity_converter_of_${entity.element.qualifiedName}"
-    }
-
-    override fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder) {
-        builder.apply {
-            val cursorParam = ParameterSpec
-                    .builder(AndroidTypeNames.CURSOR, "cursor").build()
-            addParameter(cursorParam)
-            addModifiers(PRIVATE)
-            returns(entity.typeName)
-            addCode(buildConvertMethodBody(writer, cursorParam))
-        }
-    }
-
-    private fun depth(parent: EmbeddedField?): Int {
-        return if (parent == null) {
-            0
-        } else {
-            1 + depth(parent.parent)
-        }
-    }
-
-    private fun buildConvertMethodBody(writer: ClassWriter, cursorParam: ParameterSpec): CodeBlock {
-        val scope = CodeGenScope(writer)
-        val entityVar = scope.getTmpVar("_entity")
-        scope.builder().apply {
-            scope.builder().addStatement("final $T $L", entity.typeName, entityVar)
-            val fieldsWithIndices = entity.fields.map {
-                val indexVar = scope.getTmpVar(
-                        "_cursorIndexOf${it.name.stripNonJava().capitalize()}")
-                scope.builder().addStatement("final $T $L = $N.getColumnIndex($S)",
-                        TypeName.INT, indexVar, cursorParam, it.columnName)
-                FieldWithIndex(field = it,
-                        indexVar = indexVar,
-                        alwaysExists = false)
-            }
-            FieldReadWriteWriter.readFromCursor(
-                    outVar = entityVar,
-                    outPojo = entity,
-                    cursorVar = cursorParam.name,
-                    fieldsWithIndices = fieldsWithIndices,
-                    relationCollectors = emptyList(), // no relationship for entities
-                    scope = scope)
-            addStatement("return $L", entityVar)
-        }
-        return scope.builder().build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeleteComparator.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeleteComparator.kt
deleted file mode 100644
index a2bb737..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeleteComparator.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2018 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.writer
-
-import android.arch.persistence.room.vo.Entity
-
-/**
- * Sorts the entities by their foreign key dependencies. For example, when Entity A depends on
- * Entity B, A is ordered before B.
- */
-class EntityDeleteComparator : Comparator<Entity> {
-
-    override fun compare(lhs: Entity, rhs: Entity): Int {
-        val ltr = lhs.shouldBeDeletedAfter(rhs)
-        val rtl = rhs.shouldBeDeletedAfter(lhs)
-        return when {
-            ltr == rtl -> 0
-            ltr -> -1
-            rtl -> 1
-            else -> 0 // Never happens
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeletionAdapterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeletionAdapterWriter.kt
deleted file mode 100644
index 29ba984..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityDeletionAdapterWriter.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.FieldWithIndex
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier.PUBLIC
-
-class EntityDeletionAdapterWriter(val entity: Entity) {
-    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
-        @Suppress("RemoveSingleExpressionStringTemplate")
-        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
-            superclass(ParameterizedTypeName.get(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER,
-                    entity.typeName)
-            )
-            addMethod(MethodSpec.methodBuilder("createQuery").apply {
-                addAnnotation(Override::class.java)
-                returns(ClassName.get("java.lang", "String"))
-                addModifiers(PUBLIC)
-                val query = "DELETE FROM `${entity.tableName}` WHERE " +
-                        entity.primaryKey.fields.joinToString(" AND ") {
-                            "`${it.columnName}` = ?"
-                        }
-                addStatement("return $S", query)
-            }.build())
-            addMethod(MethodSpec.methodBuilder("bind").apply {
-                val bindScope = CodeGenScope(classWriter)
-                addAnnotation(Override::class.java)
-                val stmtParam = "stmt"
-                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
-                        stmtParam).build())
-                val valueParam = "value"
-                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
-                returns(TypeName.VOID)
-                addModifiers(PUBLIC)
-                val mapped = FieldWithIndex.byOrder(entity.primaryKey.fields)
-                FieldReadWriteWriter.bindToStatement(ownerVar = valueParam,
-                        stmtParamVar = stmtParam,
-                        fieldsWithIndices = mapped,
-                        scope = bindScope)
-                addCode(bindScope.builder().build())
-            }.build())
-        }.build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityInsertionAdapterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityInsertionAdapterWriter.kt
deleted file mode 100644
index 26ff47b..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityInsertionAdapterWriter.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.FieldWithIndex
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier.PUBLIC
-
-class EntityInsertionAdapterWriter(val entity: Entity, val onConflict: String) {
-    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
-        @Suppress("RemoveSingleExpressionStringTemplate")
-        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
-            superclass(
-                    ParameterizedTypeName.get(RoomTypeNames.INSERTION_ADAPTER, entity.typeName)
-            )
-
-            // If there is an auto-increment primary key with primitive type, we consider 0 as
-            // not set. For such fields, we must generate a slightly different insertion SQL.
-            val primitiveAutoGenerateField = if (entity.primaryKey.autoGenerateId) {
-                entity.primaryKey.fields.firstOrNull()?.let { field ->
-                    field.statementBinder?.typeMirror()?.let { binderType ->
-                        if (binderType.kind.isPrimitive) {
-                            field
-                        } else {
-                            null
-                        }
-                    }
-                }
-            } else {
-                null
-            }
-            addMethod(MethodSpec.methodBuilder("createQuery").apply {
-                addAnnotation(Override::class.java)
-                returns(ClassName.get("java.lang", "String"))
-                addModifiers(PUBLIC)
-                val query =
-                        "INSERT OR $onConflict INTO `${entity.tableName}`(" +
-                                entity.fields.joinToString(",") {
-                                    "`${it.columnName}`"
-                                } + ") VALUES (" +
-                                entity.fields.joinToString(",") {
-                                    if (primitiveAutoGenerateField == it) {
-                                        "nullif(?, 0)"
-                                    } else {
-                                        "?"
-                                    }
-                                } + ")"
-                addStatement("return $S", query)
-            }.build())
-            addMethod(MethodSpec.methodBuilder("bind").apply {
-                val bindScope = CodeGenScope(classWriter)
-                addAnnotation(Override::class.java)
-                val stmtParam = "stmt"
-                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
-                        stmtParam).build())
-                val valueParam = "value"
-                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
-                returns(TypeName.VOID)
-                addModifiers(PUBLIC)
-                val mapped = FieldWithIndex.byOrder(entity.fields)
-                FieldReadWriteWriter.bindToStatement(
-                        ownerVar = valueParam,
-                        stmtParamVar = stmtParam,
-                        fieldsWithIndices = mapped,
-                        scope = bindScope
-                )
-                addCode(bindScope.builder().build())
-            }.build())
-        }.build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityUpdateAdapterWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityUpdateAdapterWriter.kt
deleted file mode 100644
index 8dee8e1..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/EntityUpdateAdapterWriter.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.FieldWithIndex
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier.PUBLIC
-
-class EntityUpdateAdapterWriter(val entity: Entity, val onConflict: String) {
-    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
-        @Suppress("RemoveSingleExpressionStringTemplate")
-        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
-            superclass(ParameterizedTypeName.get(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER,
-                    entity.typeName)
-            )
-            addMethod(MethodSpec.methodBuilder("createQuery").apply {
-                addAnnotation(Override::class.java)
-                returns(ClassName.get("java.lang", "String"))
-                addModifiers(PUBLIC)
-                val query = "UPDATE OR $onConflict `${entity.tableName}` SET " +
-                        entity.fields.joinToString(",") { field ->
-                            "`${field.columnName}` = ?"
-                        } + " WHERE " + entity.primaryKey.fields.joinToString(" AND ") {
-                            "`${it.columnName}` = ?"
-                        }
-                addStatement("return $S", query)
-            }.build())
-            addMethod(MethodSpec.methodBuilder("bind").apply {
-                val bindScope = CodeGenScope(classWriter)
-                addAnnotation(Override::class.java)
-                val stmtParam = "stmt"
-                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
-                        stmtParam).build())
-                val valueParam = "value"
-                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
-                returns(TypeName.VOID)
-                addModifiers(PUBLIC)
-                val mappedField = FieldWithIndex.byOrder(entity.fields)
-                FieldReadWriteWriter.bindToStatement(
-                        ownerVar = valueParam,
-                        stmtParamVar = stmtParam,
-                        fieldsWithIndices = mappedField,
-                        scope = bindScope
-                )
-                val pkeyStart = entity.fields.size
-                val mappedPrimaryKeys = entity.primaryKey.fields.mapIndexed { index, field ->
-                    FieldWithIndex(field = field,
-                            indexVar = "${pkeyStart + index + 1}",
-                            alwaysExists = true)
-                }
-                FieldReadWriteWriter.bindToStatement(
-                        ownerVar = valueParam,
-                        stmtParamVar = stmtParam,
-                        fieldsWithIndices = mappedPrimaryKeys,
-                        scope = bindScope
-                )
-                addCode(bindScope.builder().build())
-            }.build())
-        }.build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/FieldReadWriteWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/FieldReadWriteWriter.kt
deleted file mode 100644
index 4aa795a..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/FieldReadWriteWriter.kt
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.defaultValue
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.CallType
-import android.arch.persistence.room.vo.Constructor
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.EmbeddedField
-import android.arch.persistence.room.vo.FieldWithIndex
-import android.arch.persistence.room.vo.Pojo
-import android.arch.persistence.room.vo.RelationCollector
-import com.squareup.javapoet.TypeName
-
-/**
- * Handles writing a field into statement or reading it form statement.
- */
-class FieldReadWriteWriter(fieldWithIndex: FieldWithIndex) {
-    val field = fieldWithIndex.field
-    val indexVar = fieldWithIndex.indexVar
-    val alwaysExists = fieldWithIndex.alwaysExists
-
-    companion object {
-        /*
-         * Get all parents including the ones which have grand children in this list but does not
-         * have any direct children in the list.
-         */
-        fun getAllParents(fields: List<Field>): Set<EmbeddedField> {
-            val allParents = mutableSetOf<EmbeddedField>()
-            fun addAllParents(field: Field) {
-                var parent = field.parent
-                while (parent != null) {
-                    if (allParents.add(parent)) {
-                        parent = parent.parent
-                    } else {
-                        break
-                    }
-                }
-            }
-            fields.forEach(::addAllParents)
-            return allParents
-        }
-
-        /**
-         * Convert the fields with indices into a Node tree so that we can recursively process
-         * them. This work is done here instead of parsing because the result may include arbitrary
-         * fields.
-         */
-        private fun createNodeTree(
-                rootVar: String,
-                fieldsWithIndices: List<FieldWithIndex>,
-                scope: CodeGenScope): Node {
-            val allParents = getAllParents(fieldsWithIndices.map { it.field })
-            val rootNode = Node(rootVar, null)
-            rootNode.directFields = fieldsWithIndices.filter { it.field.parent == null }
-            val parentNodes = allParents.associate {
-                Pair(it, Node(
-                        varName = scope.getTmpVar("_tmp${it.field.name.capitalize()}"),
-                        fieldParent = it))
-            }
-            parentNodes.values.forEach { node ->
-                val fieldParent = node.fieldParent!!
-                val grandParent = fieldParent.parent
-                val grandParentNode = grandParent?.let {
-                    parentNodes[it]
-                } ?: rootNode
-                node.directFields = fieldsWithIndices.filter { it.field.parent == fieldParent }
-                node.parentNode = grandParentNode
-                grandParentNode.subNodes.add(node)
-            }
-            return rootNode
-        }
-
-        fun bindToStatement(
-                ownerVar: String,
-                stmtParamVar: String,
-                fieldsWithIndices: List<FieldWithIndex>,
-                scope: CodeGenScope
-        ) {
-            fun visitNode(node: Node) {
-                fun bindWithDescendants() {
-                    node.directFields.forEach {
-                        FieldReadWriteWriter(it).bindToStatement(
-                                ownerVar = node.varName,
-                                stmtParamVar = stmtParamVar,
-                                scope = scope
-                        )
-                    }
-                    node.subNodes.forEach(::visitNode)
-                }
-
-                val fieldParent = node.fieldParent
-                if (fieldParent != null) {
-                    fieldParent.getter.writeGet(
-                            ownerVar = node.parentNode!!.varName,
-                            outVar = node.varName,
-                            builder = scope.builder()
-                    )
-                    scope.builder().apply {
-                        beginControlFlow("if($L != null)", node.varName).apply {
-                            bindWithDescendants()
-                        }
-                        nextControlFlow("else").apply {
-                            node.allFields().forEach {
-                                addStatement("$L.bindNull($L)", stmtParamVar, it.indexVar)
-                            }
-                        }
-                        endControlFlow()
-                    }
-                } else {
-                    bindWithDescendants()
-                }
-            }
-            visitNode(createNodeTree(ownerVar, fieldsWithIndices, scope))
-        }
-
-        /**
-         * Just constructs the given item, does NOT DECLARE. Declaration happens outside the
-         * reading statement since we may never read if the cursor does not have necessary
-         * columns.
-         */
-        private fun construct(
-                outVar: String,
-                constructor: Constructor?,
-                typeName: TypeName,
-                localVariableNames: Map<String, FieldWithIndex>,
-                localEmbeddeds: List<Node>, scope: CodeGenScope
-        ) {
-            if (constructor == null) {
-                // best hope code generation
-                scope.builder().apply {
-                    addStatement("$L = new $T()", outVar, typeName)
-                }
-                return
-            }
-            val variableNames = constructor.params.map { param ->
-                when (param) {
-                    is Constructor.FieldParam -> localVariableNames.entries.firstOrNull {
-                        it.value.field === param.field
-                    }?.key
-                    is Constructor.EmbeddedParam -> localEmbeddeds.firstOrNull {
-                        it.fieldParent === param.embedded
-                    }?.varName
-                    else -> null
-                }
-            }
-            val args = variableNames.joinToString(",") { it ?: "null" }
-            scope.builder().apply {
-                addStatement("$L = new $T($L)", outVar, typeName, args)
-            }
-        }
-
-        /**
-         * Reads the row into the given variable. It does not declare it but constructs it.
-         */
-        fun readFromCursor(
-                outVar: String,
-                outPojo: Pojo,
-                cursorVar: String,
-                fieldsWithIndices: List<FieldWithIndex>,
-                scope: CodeGenScope,
-                relationCollectors: List<RelationCollector>
-        ) {
-            fun visitNode(node: Node) {
-                val fieldParent = node.fieldParent
-                fun readNode() {
-                    // read constructor parameters into local fields
-                    val constructorFields = node.directFields.filter {
-                        it.field.setter.callType == CallType.CONSTRUCTOR
-                    }.associateBy { fwi ->
-                        FieldReadWriteWriter(fwi).readIntoTmpVar(cursorVar, scope)
-                    }
-                    // read decomposed fields
-                    node.subNodes.forEach(::visitNode)
-                    // construct the object
-                    if (fieldParent != null) {
-                        construct(outVar = node.varName,
-                                constructor = fieldParent.pojo.constructor,
-                                typeName = fieldParent.field.typeName,
-                                localEmbeddeds = node.subNodes,
-                                localVariableNames = constructorFields,
-                                scope = scope)
-                    } else {
-                        construct(outVar = node.varName,
-                                constructor = outPojo.constructor,
-                                typeName = outPojo.typeName,
-                                localEmbeddeds = node.subNodes,
-                                localVariableNames = constructorFields,
-                                scope = scope)
-                    }
-                    // ready any field that was not part of the constructor
-                    node.directFields.filterNot {
-                        it.field.setter.callType == CallType.CONSTRUCTOR
-                    }.forEach { fwi ->
-                        FieldReadWriteWriter(fwi).readFromCursor(
-                                ownerVar = node.varName,
-                                cursorVar = cursorVar,
-                                scope = scope)
-                    }
-                    // assign relationship fields which will be read later
-                    relationCollectors.filter { (relation) ->
-                        relation.field.parent === fieldParent
-                    }.forEach {
-                        it.writeReadParentKeyCode(
-                                cursorVarName = cursorVar,
-                                itemVar = node.varName,
-                                fieldsWithIndices = fieldsWithIndices,
-                                scope = scope)
-                    }
-                    // assign sub modes to fields if they were not part of the constructor.
-                    node.subNodes.map {
-                        val setter = it.fieldParent?.setter
-                        if (setter != null && setter.callType != CallType.CONSTRUCTOR) {
-                            Pair(it.varName, setter)
-                        } else {
-                            null
-                        }
-                    }.filterNotNull().forEach { (varName, setter) ->
-                        setter.writeSet(
-                                ownerVar = node.varName,
-                                inVar = varName,
-                                builder = scope.builder())
-                    }
-                }
-                if (fieldParent == null) {
-                    // root element
-                    // always declared by the caller so we don't declare this
-                    readNode()
-                } else {
-                    // always declare, we'll set below
-                    scope.builder().addStatement("final $T $L", fieldParent.pojo.typeName,
-                                        node.varName)
-                    if (fieldParent.nonNull) {
-                        readNode()
-                    } else {
-                        val myDescendants = node.allFields()
-                        val allNullCheck = myDescendants.joinToString(" && ") {
-                            if (it.alwaysExists) {
-                                "$cursorVar.isNull(${it.indexVar})"
-                            } else {
-                                "( ${it.indexVar} == -1 || $cursorVar.isNull(${it.indexVar}))"
-                            }
-                        }
-                        scope.builder().apply {
-                            beginControlFlow("if (! ($L))", allNullCheck).apply {
-                                readNode()
-                            }
-                            nextControlFlow(" else ").apply {
-                                addStatement("$L = null", node.varName)
-                            }
-                            endControlFlow()
-                        }
-                    }
-                }
-            }
-            visitNode(createNodeTree(outVar, fieldsWithIndices, scope))
-        }
-    }
-
-    /**
-     * @param ownerVar The entity / pojo that owns this field. It must own this field! (not the
-     * container pojo)
-     * @param stmtParamVar The statement variable
-     * @param scope The code generation scope
-     */
-    private fun bindToStatement(ownerVar: String, stmtParamVar: String, scope: CodeGenScope) {
-        field.statementBinder?.let { binder ->
-            val varName = if (field.getter.callType == CallType.FIELD) {
-                "$ownerVar.${field.name}"
-            } else {
-                "$ownerVar.${field.getter.name}()"
-            }
-            binder.bindToStmt(stmtParamVar, indexVar, varName, scope)
-        }
-    }
-
-    /**
-     * @param ownerVar The entity / pojo that owns this field. It must own this field (not the
-     * container pojo)
-     * @param cursorVar The cursor variable
-     * @param scope The code generation scope
-     */
-    private fun readFromCursor(ownerVar: String, cursorVar: String, scope: CodeGenScope) {
-        fun toRead() {
-            field.cursorValueReader?.let { reader ->
-                scope.builder().apply {
-                    when (field.setter.callType) {
-                        CallType.FIELD -> {
-                            reader.readFromCursor("$ownerVar.${field.getter.name}", cursorVar,
-                                    indexVar, scope)
-                        }
-                        CallType.METHOD -> {
-                            val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
-                            addStatement("final $T $L", field.getter.type.typeName(), tmpField)
-                            reader.readFromCursor(tmpField, cursorVar, indexVar, scope)
-                            addStatement("$L.$L($L)", ownerVar, field.setter.name, tmpField)
-                        }
-                        CallType.CONSTRUCTOR -> {
-                            // no-op
-                        }
-                    }
-                }
-            }
-        }
-        if (alwaysExists) {
-            toRead()
-        } else {
-            scope.builder().apply {
-                beginControlFlow("if ($L != -1)", indexVar).apply {
-                    toRead()
-                }
-                endControlFlow()
-            }
-        }
-    }
-
-    /**
-     * Reads the value into a temporary local variable.
-     */
-    fun readIntoTmpVar(cursorVar: String, scope: CodeGenScope): String {
-        val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
-        val typeName = field.getter.type.typeName()
-        scope.builder().apply {
-            addStatement("final $T $L", typeName, tmpField)
-            if (alwaysExists) {
-                field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
-            } else {
-                beginControlFlow("if ($L == -1)", indexVar).apply {
-                    addStatement("$L = $L", tmpField, typeName.defaultValue())
-                }
-                nextControlFlow("else").apply {
-                    field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
-                }
-                endControlFlow()
-            }
-        }
-        return tmpField
-    }
-
-    /**
-     * On demand node which is created based on the fields that were passed into this class.
-     */
-    private class Node(
-            // root for me
-            val varName: String,
-            // set if I'm a FieldParent
-            val fieldParent: EmbeddedField?
-    ) {
-        // whom do i belong
-        var parentNode: Node? = null
-        // these fields are my direct fields
-        lateinit var directFields: List<FieldWithIndex>
-        // these nodes are under me
-        val subNodes = mutableListOf<Node>()
-
-        fun allFields(): List<FieldWithIndex> {
-            return directFields + subNodes.flatMap { it.allFields() }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/PreparedStatementWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/PreparedStatementWriter.kt
deleted file mode 100644
index 7982ace..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/PreparedStatementWriter.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.solver.CodeGenScope
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier
-
-/**
- * Creates anonymous classes for RoomTypeNames#SHARED_SQLITE_STMT.
- */
-class PreparedStatementWriter(val queryWriter: QueryWriter) {
-    fun createAnonymous(classWriter: ClassWriter, dbParam: FieldSpec): TypeSpec {
-        val scope = CodeGenScope(classWriter)
-        @Suppress("RemoveSingleExpressionStringTemplate")
-        return TypeSpec.anonymousClassBuilder("$N", dbParam).apply {
-            superclass(RoomTypeNames.SHARED_SQLITE_STMT)
-            addMethod(MethodSpec.methodBuilder("createQuery").apply {
-                addAnnotation(Override::class.java)
-                returns(ClassName.get("java.lang", "String"))
-                addModifiers(Modifier.PUBLIC)
-                val queryName = scope.getTmpVar("_query")
-                val queryGenScope = scope.fork()
-                queryWriter.prepareQuery(queryName, queryGenScope)
-                addCode(queryGenScope.builder().build())
-                addStatement("return $L", queryName)
-            }.build())
-        }.build()
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/QueryWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/QueryWriter.kt
deleted file mode 100644
index d99e989..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/QueryWriter.kt
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.RoomTypeNames.ROOM_SQL_QUERY
-import android.arch.persistence.room.ext.RoomTypeNames.STRING_UTIL
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.ParsedQuery
-import android.arch.persistence.room.parser.Section
-import android.arch.persistence.room.parser.SectionType.BIND_VAR
-import android.arch.persistence.room.parser.SectionType.NEWLINE
-import android.arch.persistence.room.parser.SectionType.TEXT
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.QueryMethod
-import android.arch.persistence.room.vo.QueryParameter
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-
-/**
- * Writes the SQL query and arguments for a QueryMethod.
- */
-class QueryWriter constructor(val parameters: List<QueryParameter>,
-                              val sectionToParamMapping: List<Pair<Section, QueryParameter?>>,
-                              val query: ParsedQuery) {
-
-    constructor(queryMethod: QueryMethod) : this(queryMethod.parameters,
-            queryMethod.sectionToParamMapping, queryMethod.query) {
-    }
-
-    fun prepareReadAndBind(outSqlQueryName: String, outRoomSQLiteQueryVar: String,
-                           scope: CodeGenScope) {
-        val listSizeVars = createSqlQueryAndArgs(outSqlQueryName, outRoomSQLiteQueryVar, scope)
-        bindArgs(outRoomSQLiteQueryVar, listSizeVars, scope)
-    }
-
-    fun prepareQuery(
-            outSqlQueryName: String, scope: CodeGenScope): List<Pair<QueryParameter, String>> {
-        return createSqlQueryAndArgs(outSqlQueryName, null, scope)
-    }
-
-    private fun createSqlQueryAndArgs(
-            outSqlQueryName: String,
-            outArgsName: String?,
-            scope: CodeGenScope
-    ): List<Pair<QueryParameter, String>> {
-        val listSizeVars = arrayListOf<Pair<QueryParameter, String>>()
-        val varargParams = parameters
-                .filter { it.queryParamAdapter?.isMultiple ?: false }
-        val sectionToParamMapping = sectionToParamMapping
-        val knownQueryArgsCount = sectionToParamMapping.filterNot {
-            it.second?.queryParamAdapter?.isMultiple ?: false
-        }.size
-        scope.builder().apply {
-            if (varargParams.isNotEmpty()) {
-                val stringBuilderVar = scope.getTmpVar("_stringBuilder")
-                addStatement("$T $L = $T.newStringBuilder()",
-                        ClassName.get(StringBuilder::class.java), stringBuilderVar, STRING_UTIL)
-                query.sections.forEach {
-                    when (it.type) {
-                        TEXT -> addStatement("$L.append($S)", stringBuilderVar, it.text)
-                        NEWLINE -> addStatement("$L.append($S)", stringBuilderVar, "\n")
-                        BIND_VAR -> {
-                            // If it is null, will be reported as error before. We just try out
-                            // best to generate as much code as possible.
-                            sectionToParamMapping.firstOrNull { mapping ->
-                                mapping.first == it
-                            }?.let { pair ->
-                                if (pair.second?.queryParamAdapter?.isMultiple ?: false) {
-                                    val tmpCount = scope.getTmpVar("_inputSize")
-                                    listSizeVars.add(Pair(pair.second!!, tmpCount))
-                                    pair.second
-                                            ?.queryParamAdapter
-                                            ?.getArgCount(pair.second!!.name, tmpCount, scope)
-                                    addStatement("$T.appendPlaceholders($L, $L)",
-                                            STRING_UTIL, stringBuilderVar, tmpCount)
-                                } else {
-                                    addStatement("$L.append($S)", stringBuilderVar, "?")
-                                }
-                            }
-                        }
-                    }
-                }
-
-                addStatement("final $T $L = $L.toString()", String::class.typeName(),
-                        outSqlQueryName, stringBuilderVar)
-                if (outArgsName != null) {
-                    val argCount = scope.getTmpVar("_argCount")
-                    addStatement("final $T $L = $L$L", TypeName.INT, argCount, knownQueryArgsCount,
-                            listSizeVars.joinToString("") { " + ${it.second}" })
-                    addStatement("final $T $L = $T.acquire($L, $L)",
-                            ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
-                            argCount)
-                }
-            } else {
-                addStatement("final $T $L = $S", String::class.typeName(),
-                        outSqlQueryName, query.queryWithReplacedBindParams)
-                if (outArgsName != null) {
-                    addStatement("final $T $L = $T.acquire($L, $L)",
-                            ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
-                            knownQueryArgsCount)
-                }
-            }
-        }
-        return listSizeVars
-    }
-
-    fun bindArgs(
-            outArgsName: String,
-            listSizeVars: List<Pair<QueryParameter, String>>,
-            scope: CodeGenScope
-    ) {
-        if (parameters.isEmpty()) {
-            return
-        }
-        scope.builder().apply {
-            val argIndex = scope.getTmpVar("_argIndex")
-            addStatement("$T $L = $L", TypeName.INT, argIndex, 1)
-            // # of bindings with 1 placeholder
-            var constInputs = 0
-            // variable names for size of the bindings that have multiple  args
-            val varInputs = arrayListOf<String>()
-            sectionToParamMapping.forEach { pair ->
-                // reset the argIndex to the correct start index
-                if (constInputs > 0 || varInputs.isNotEmpty()) {
-                    addStatement("$L = $L$L", argIndex,
-                            if (constInputs > 0) (1 + constInputs) else "1",
-                            varInputs.joinToString("") { " + $it" })
-                }
-                val param = pair.second
-                param?.let {
-                    param.queryParamAdapter?.bindToStmt(param.name, outArgsName, argIndex, scope)
-                }
-                // add these to the list so that we can use them to calculate the next count.
-                val sizeVar = listSizeVars.firstOrNull { it.first == param }
-                if (sizeVar == null) {
-                    constInputs ++
-                } else {
-                    varInputs.add(sizeVar.second)
-                }
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/RelationCollectorMethodWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/RelationCollectorMethodWriter.kt
deleted file mode 100644
index ecd9b59..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/RelationCollectorMethodWriter.kt
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.AndroidTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.RelationCollector
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import stripNonJava
-import javax.lang.model.element.Modifier
-
-/**
- * Writes the method that fetches the relations of a POJO and assigns them into the given map.
- */
-class RelationCollectorMethodWriter(private val collector: RelationCollector)
-    : ClassWriter.SharedMethodSpec(
-        "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
-                "As${collector.relation.pojoTypeName.toString().stripNonJava()}") {
-    companion object {
-        val KEY_SET_VARIABLE = "__mapKeySet"
-    }
-    override fun getUniqueKey(): String {
-        val relation = collector.relation
-        return "RelationCollectorMethodWriter" +
-                "-${collector.mapTypeName}" +
-                "-${relation.entity.typeName}" +
-                "-${relation.entityField.columnName}" +
-                "-${relation.pojoTypeName}" +
-                "-${relation.createLoadAllSql()}"
-    }
-
-    override fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder) {
-        val scope = CodeGenScope(writer)
-        val relation = collector.relation
-
-        val param = ParameterSpec.builder(collector.mapTypeName, "_map")
-                .addModifiers(Modifier.FINAL)
-                .build()
-        val sqlQueryVar = scope.getTmpVar("_sql")
-        val keySetVar = KEY_SET_VARIABLE
-
-        val cursorVar = "_cursor"
-        val itemKeyIndexVar = "_itemKeyIndex"
-        val stmtVar = scope.getTmpVar("_stmt")
-        scope.builder().apply {
-
-            val keySetType = ParameterizedTypeName.get(
-                    ClassName.get(Set::class.java), collector.keyTypeName
-            )
-            addStatement("final $T $L = $N.keySet()", keySetType, keySetVar, param)
-            beginControlFlow("if ($L.isEmpty())", keySetVar).apply {
-                addStatement("return")
-            }
-            endControlFlow()
-            addStatement("// check if the size is too big, if so divide")
-            beginControlFlow("if($N.size() > $T.MAX_BIND_PARAMETER_CNT)",
-                    param, RoomTypeNames.ROOM_DB).apply {
-                // divide it into chunks
-                val tmpMapVar = scope.getTmpVar("_tmpInnerMap")
-                addStatement("$T $L = new $T($L.MAX_BIND_PARAMETER_CNT)",
-                        collector.mapTypeName, tmpMapVar,
-                        collector.mapTypeName, RoomTypeNames.ROOM_DB)
-                val mapIndexVar = scope.getTmpVar("_mapIndex")
-                val tmpIndexVar = scope.getTmpVar("_tmpIndex")
-                val limitVar = scope.getTmpVar("_limit")
-                addStatement("$T $L = 0", TypeName.INT, mapIndexVar)
-                addStatement("$T $L = 0", TypeName.INT, tmpIndexVar)
-                addStatement("final $T $L = $N.size()", TypeName.INT, limitVar, param)
-                beginControlFlow("while($L < $L)", mapIndexVar, limitVar).apply {
-                    addStatement("$L.put($N.keyAt($L), $N.valueAt($L))",
-                            tmpMapVar, param, mapIndexVar, param, mapIndexVar)
-                    addStatement("$L++", mapIndexVar)
-                    addStatement("$L++", tmpIndexVar)
-                    beginControlFlow("if($L == $T.MAX_BIND_PARAMETER_CNT)",
-                            tmpIndexVar, RoomTypeNames.ROOM_DB).apply {
-                        // recursively load that batch
-                        addStatement("$L($L)", methodName, tmpMapVar)
-                        // clear nukes the backing data hence we create a new one
-                        addStatement("$L = new $T($T.MAX_BIND_PARAMETER_CNT)",
-                                tmpMapVar, collector.mapTypeName, RoomTypeNames.ROOM_DB)
-                        addStatement("$L = 0", tmpIndexVar)
-                    }.endControlFlow()
-                }.endControlFlow()
-                beginControlFlow("if($L > 0)", tmpIndexVar).apply {
-                    // load the last batch
-                    addStatement("$L($L)", methodName, tmpMapVar)
-                }.endControlFlow()
-                addStatement("return")
-            }.endControlFlow()
-            collector.queryWriter.prepareReadAndBind(sqlQueryVar, stmtVar, scope)
-
-            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
-                    DaoWriter.dbField, stmtVar)
-
-            beginControlFlow("try").apply {
-                addStatement("final $T $L = $L.getColumnIndex($S)",
-                        TypeName.INT, itemKeyIndexVar, cursorVar, relation.entityField.columnName)
-
-                beginControlFlow("if ($L == -1)", itemKeyIndexVar).apply {
-                    addStatement("return")
-                }
-                endControlFlow()
-
-                collector.rowAdapter.onCursorReady(cursorVar, scope)
-                val tmpVarName = scope.getTmpVar("_item")
-                beginControlFlow("while($L.moveToNext())", cursorVar).apply {
-                    // read key from the cursor
-                    collector.readKey(
-                            cursorVarName = cursorVar,
-                            indexVar = itemKeyIndexVar,
-                            scope = scope
-                    ) { keyVar ->
-                        val collectionVar = scope.getTmpVar("_tmpCollection")
-                        addStatement("$T $L = $N.get($L)", collector.collectionTypeName,
-                                collectionVar, param, keyVar)
-                        beginControlFlow("if ($L != null)", collectionVar).apply {
-                            addStatement("final $T $L", relation.pojoTypeName, tmpVarName)
-                            collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
-                            addStatement("$L.add($L)", collectionVar, tmpVarName)
-                        }
-                        endControlFlow()
-                    }
-                }
-                endControlFlow()
-                collector.rowAdapter.onCursorFinished()?.invoke(scope)
-            }
-            nextControlFlow("finally").apply {
-                addStatement("$L.close()", cursorVar)
-            }
-            endControlFlow()
-        }
-        builder.apply {
-            addModifiers(Modifier.PRIVATE)
-            addParameter(param)
-            returns(TypeName.VOID)
-            addCode(scope.builder().build())
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriter.kt
deleted file mode 100644
index 09d6d52..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriter.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.Database
-import android.arch.persistence.room.vo.Entity
-import android.support.annotation.VisibleForTesting
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Modifier.PROTECTED
-import javax.lang.model.element.Modifier.PUBLIC
-
-/**
- * Create an open helper using SupportSQLiteOpenHelperFactory
- */
-class SQLiteOpenHelperWriter(val database: Database) {
-    fun write(outVar: String, configuration: ParameterSpec, scope: CodeGenScope) {
-        scope.builder().apply {
-            val sqliteConfigVar = scope.getTmpVar("_sqliteConfig")
-            val callbackVar = scope.getTmpVar("_openCallback")
-            addStatement("final $T $L = new $T($N, $L, $S, $S)",
-                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CALLBACK,
-                    callbackVar, RoomTypeNames.OPEN_HELPER, configuration,
-                    createOpenCallback(scope), database.identityHash, database.legacyIdentityHash)
-            // build configuration
-            addStatement(
-                    """
-                    final $T $L = $T.builder($N.context)
-                    .name($N.name)
-                    .callback($L)
-                    .build()
-                    """.trimIndent(),
-                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG, sqliteConfigVar,
-                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG,
-                    configuration, configuration, callbackVar)
-            addStatement("final $T $N = $N.sqliteOpenHelperFactory.create($L)",
-                    SupportDbTypeNames.SQLITE_OPEN_HELPER, outVar,
-                    configuration, sqliteConfigVar)
-        }
-    }
-
-    private fun createOpenCallback(scope: CodeGenScope): TypeSpec {
-        return TypeSpec.anonymousClassBuilder(L, database.version).apply {
-            superclass(RoomTypeNames.OPEN_HELPER_DELEGATE)
-            addMethod(createCreateAllTables())
-            addMethod(createDropAllTables())
-            addMethod(createOnCreate(scope.fork()))
-            addMethod(createOnOpen(scope.fork()))
-            addMethod(createValidateMigration(scope.fork()))
-        }.build()
-    }
-
-    private fun createValidateMigration(scope: CodeGenScope): MethodSpec {
-        return MethodSpec.methodBuilder("validateMigration").apply {
-            addModifiers(PROTECTED)
-            addAnnotation(Override::class.java)
-            val dbParam = ParameterSpec.builder(SupportDbTypeNames.DB, "_db").build()
-            addParameter(dbParam)
-            database.entities.forEach { entity ->
-                val methodScope = scope.fork()
-                TableInfoValidationWriter(entity).write(dbParam, methodScope)
-                addCode(methodScope.builder().build())
-            }
-        }.build()
-    }
-
-    private fun createOnCreate(scope: CodeGenScope): MethodSpec {
-        return MethodSpec.methodBuilder("onCreate").apply {
-            addModifiers(PROTECTED)
-            addAnnotation(Override::class.java)
-            addParameter(SupportDbTypeNames.DB, "_db")
-            invokeCallbacks(scope, "onCreate")
-        }.build()
-    }
-
-    private fun createOnOpen(scope: CodeGenScope): MethodSpec {
-        return MethodSpec.methodBuilder("onOpen").apply {
-            addModifiers(PUBLIC)
-            addAnnotation(Override::class.java)
-            addParameter(SupportDbTypeNames.DB, "_db")
-            addStatement("mDatabase = _db")
-            if (database.enableForeignKeys) {
-                addStatement("_db.execSQL($S)", "PRAGMA foreign_keys = ON")
-            }
-            addStatement("internalInitInvalidationTracker(_db)")
-            invokeCallbacks(scope, "onOpen")
-        }.build()
-    }
-
-    private fun createCreateAllTables(): MethodSpec {
-        return MethodSpec.methodBuilder("createAllTables").apply {
-            addModifiers(PUBLIC)
-            addAnnotation(Override::class.java)
-            addParameter(SupportDbTypeNames.DB, "_db")
-            database.bundle.buildCreateQueries().forEach {
-                addStatement("_db.execSQL($S)", it)
-            }
-        }.build()
-    }
-
-    private fun createDropAllTables(): MethodSpec {
-        return MethodSpec.methodBuilder("dropAllTables").apply {
-            addModifiers(PUBLIC)
-            addAnnotation(Override::class.java)
-            addParameter(SupportDbTypeNames.DB, "_db")
-            database.entities.forEach {
-                addStatement("_db.execSQL($S)", createDropTableQuery(it))
-            }
-        }.build()
-    }
-
-    private fun MethodSpec.Builder.invokeCallbacks(scope: CodeGenScope, methodName: String) {
-        val iVar = scope.getTmpVar("_i")
-        val sizeVar = scope.getTmpVar("_size")
-        beginControlFlow("if (mCallbacks != null)").apply {
-            beginControlFlow("for (int $N = 0, $N = mCallbacks.size(); $N < $N; $N++)",
-                    iVar, sizeVar, iVar, sizeVar, iVar).apply {
-                addStatement("mCallbacks.get($N).$N(_db)", iVar, methodName)
-            }
-            endControlFlow()
-        }
-        endControlFlow()
-    }
-
-    @VisibleForTesting
-    fun createQuery(entity: Entity): String {
-        return entity.createTableQuery
-    }
-
-    @VisibleForTesting
-    fun createDropTableQuery(entity: Entity): String {
-        return "DROP TABLE IF EXISTS `${entity.tableName}`"
-    }
-}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/TableInfoValidationWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/TableInfoValidationWriter.kt
deleted file mode 100644
index 3c67475..0000000
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/TableInfoValidationWriter.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.N
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.vo.Entity
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import stripNonJava
-import java.util.Arrays
-import java.util.HashMap
-import java.util.HashSet
-
-class TableInfoValidationWriter(val entity: Entity) {
-    fun write(dbParam: ParameterSpec, scope: CodeGenScope) {
-        val suffix = entity.tableName.stripNonJava().capitalize()
-        val expectedInfoVar = scope.getTmpVar("_info$suffix")
-        scope.builder().apply {
-            val columnListVar = scope.getTmpVar("_columns$suffix")
-            val columnListType = ParameterizedTypeName.get(HashMap::class.typeName(),
-                    CommonTypeNames.STRING, RoomTypeNames.TABLE_INFO_COLUMN)
-
-            addStatement("final $T $L = new $T($L)", columnListType, columnListVar,
-                    columnListType, entity.fields.size)
-            entity.fields.forEach { field ->
-                addStatement("$L.put($S, new $T($S, $S, $L, $L))",
-                        columnListVar, field.columnName, RoomTypeNames.TABLE_INFO_COLUMN,
-                        /*name*/ field.columnName,
-                        /*type*/ field.affinity?.name ?: SQLTypeAffinity.TEXT.name,
-                        /*nonNull*/ field.nonNull,
-                        /*pkeyPos*/ entity.primaryKey.fields.indexOf(field) + 1)
-            }
-
-            val foreignKeySetVar = scope.getTmpVar("_foreignKeys$suffix")
-            val foreignKeySetType = ParameterizedTypeName.get(HashSet::class.typeName(),
-                    RoomTypeNames.TABLE_INFO_FOREIGN_KEY)
-            addStatement("final $T $L = new $T($L)", foreignKeySetType, foreignKeySetVar,
-                    foreignKeySetType, entity.foreignKeys.size)
-            entity.foreignKeys.forEach {
-                val myColumnNames = it.childFields
-                        .joinToString(",") { "\"${it.columnName}\"" }
-                val refColumnNames = it.parentColumns
-                        .joinToString(",") { "\"$it\"" }
-                addStatement("$L.add(new $T($S, $S, $S," +
-                        "$T.asList($L), $T.asList($L)))", foreignKeySetVar,
-                        RoomTypeNames.TABLE_INFO_FOREIGN_KEY,
-                        /*parent table*/ it.parentTable,
-                        /*on delete*/ it.onDelete.sqlName,
-                        /*on update*/ it.onUpdate.sqlName,
-                        Arrays::class.typeName(),
-                        /*parent names*/ myColumnNames,
-                        Arrays::class.typeName(),
-                        /*parent column names*/ refColumnNames)
-            }
-
-            val indicesSetVar = scope.getTmpVar("_indices$suffix")
-            val indicesType = ParameterizedTypeName.get(HashSet::class.typeName(),
-                    RoomTypeNames.TABLE_INFO_INDEX)
-            addStatement("final $T $L = new $T($L)", indicesType, indicesSetVar,
-                    indicesType, entity.indices.size)
-            entity.indices.forEach { index ->
-                val columnNames = index.fields
-                        .joinToString(",") { "\"${it.columnName}\"" }
-                addStatement("$L.add(new $T($S, $L, $T.asList($L)))",
-                        indicesSetVar,
-                        RoomTypeNames.TABLE_INFO_INDEX,
-                        index.name,
-                        index.unique,
-                        Arrays::class.typeName(),
-                        columnNames)
-            }
-
-            addStatement("final $T $L = new $T($S, $L, $L, $L)",
-                    RoomTypeNames.TABLE_INFO, expectedInfoVar, RoomTypeNames.TABLE_INFO,
-                    entity.tableName, columnListVar, foreignKeySetVar, indicesSetVar)
-
-            val existingVar = scope.getTmpVar("_existing$suffix")
-            addStatement("final $T $L = $T.read($N, $S)",
-                    RoomTypeNames.TABLE_INFO, existingVar, RoomTypeNames.TABLE_INFO,
-                    dbParam, entity.tableName)
-
-            beginControlFlow("if (! $L.equals($L))", expectedInfoVar, existingVar).apply {
-                addStatement("throw new $T($S + $L + $S + $L)",
-                        IllegalStateException::class.typeName(),
-                        "Migration didn't properly handle ${entity.tableName}" +
-                                "(${entity.element.qualifiedName}).\n Expected:\n",
-                        expectedInfoVar, "\n Found:\n", existingVar)
-            }
-            endControlFlow()
-        }
-    }
-}
diff --git a/room/compiler/src/main/kotlin/androidx/room/RoomProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/RoomProcessor.kt
new file mode 100644
index 0000000..eec5e9f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/RoomProcessor.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import androidx.room.processor.Context
+import androidx.room.processor.DatabaseProcessor
+import androidx.room.processor.ProcessorErrors
+import androidx.room.vo.DaoMethod
+import androidx.room.vo.Warning
+import androidx.room.writer.DaoWriter
+import androidx.room.writer.DatabaseWriter
+import com.google.auto.common.BasicAnnotationProcessor
+import com.google.auto.common.MoreElements
+import com.google.common.collect.SetMultimap
+import java.io.File
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.Element
+
+/**
+ * The annotation processor for Room.
+ */
+class RoomProcessor : BasicAnnotationProcessor() {
+    override fun initSteps(): MutableIterable<ProcessingStep>? {
+        val context = Context(processingEnv)
+        return arrayListOf(DatabaseProcessingStep(context))
+    }
+
+    override fun getSupportedOptions(): MutableSet<String> {
+        return Context.ARG_OPTIONS.toMutableSet()
+    }
+
+    override fun getSupportedSourceVersion(): SourceVersion {
+        return SourceVersion.latest()
+    }
+
+    class DatabaseProcessingStep(context: Context) : ContextBoundProcessingStep(context) {
+        override fun process(
+                elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>
+        ): MutableSet<Element> {
+            // TODO multi step support
+            val databases = elementsByAnnotation[Database::class.java]
+                    ?.map {
+                        DatabaseProcessor(context, MoreElements.asType(it)).process()
+                    }
+            val allDaoMethods = databases?.flatMap { it.daoMethods }
+            allDaoMethods?.let {
+                prepareDaosForWriting(databases, it)
+                it.forEach {
+                    DaoWriter(it.dao, context.processingEnv).write(context.processingEnv)
+                }
+            }
+
+            databases?.forEach { db ->
+                DatabaseWriter(db).write(context.processingEnv)
+                if (db.exportSchema) {
+                    val schemaOutFolder = context.schemaOutFolder
+                    if (schemaOutFolder == null) {
+                        context.logger.w(Warning.MISSING_SCHEMA_LOCATION, db.element,
+                                ProcessorErrors.MISSING_SCHEMA_EXPORT_DIRECTORY)
+                    } else {
+                        if (!schemaOutFolder.exists()) {
+                            schemaOutFolder.mkdirs()
+                        }
+                        val qName = db.element.qualifiedName.toString()
+                        val dbSchemaFolder = File(schemaOutFolder, qName)
+                        if (!dbSchemaFolder.exists()) {
+                            dbSchemaFolder.mkdirs()
+                        }
+                        db.exportSchema(File(dbSchemaFolder, "${db.version}.json"))
+                    }
+                }
+            }
+            return mutableSetOf()
+        }
+        override fun annotations(): MutableSet<out Class<out Annotation>> {
+            return mutableSetOf(Database::class.java, Dao::class.java, Entity::class.java)
+        }
+
+        /**
+         * Traverses all dao methods and assigns them suffix if they are used in multiple databases.
+         */
+        private fun prepareDaosForWriting(
+                databases: List<androidx.room.vo.Database>,
+                daoMethods: List<DaoMethod>) {
+            daoMethods.groupBy { it.dao.typeName }
+                    // if used only in 1 database, nothing to do.
+                    .filter { entry -> entry.value.size > 1 }
+                    .forEach { entry ->
+                        entry.value.groupBy { daoMethod ->
+                            // first suffix guess: Database's simple name
+                            val db = databases.first { db -> db.daoMethods.contains(daoMethod) }
+                            db.typeName.simpleName()
+                        }.forEach { (dbName, methods) ->
+                            if (methods.size == 1) {
+                                //good, db names do not clash, use db name as suffix
+                                methods.first().dao.setSuffix(dbName)
+                            } else {
+                                // ok looks like a dao is used in 2 different databases both of
+                                // which have the same name. enumerate.
+                                methods.forEachIndexed { index, method ->
+                                    method.dao.setSuffix("${dbName}_$index")
+                                }
+                            }
+                        }
+                    }
+        }
+    }
+
+    abstract class ContextBoundProcessingStep(val context: Context) : ProcessingStep
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/KotlinMetadataProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/ext/KotlinMetadataProcessor.kt
new file mode 100644
index 0000000..9177c23
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/KotlinMetadataProcessor.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 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 androidx.room.ext
+
+import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
+import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
+import me.eugeniomarletti.kotlin.metadata.jvm.getJvmConstructorSignature
+import org.jetbrains.kotlin.serialization.ProtoBuf
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * Utility interface for processors that wants to run kotlin specific code.
+ */
+interface KotlinMetadataProcessor : KotlinMetadataUtils {
+    /**
+     * Returns the parameter names of the function if all have names embedded in the metadata.
+     */
+    fun KotlinClassMetadata.getParameterNames(method: ExecutableElement): List<String>? {
+        val valueParameterList = this.data.getFunctionOrNull(method)?.valueParameterList
+                ?: findConstructor(method)?.valueParameterList
+                ?: return null
+        return if (valueParameterList.all { it.hasName() }) {
+            valueParameterList.map {
+                data.nameResolver.getName(it.name)
+                        .asString()
+                        .replace("`", "")
+                        .removeSuffix("?")
+                        .trim()
+            }
+        } else {
+            null
+        }
+    }
+
+    /**
+     * Finds the kotlin metadata for a constructor.
+     */
+    private fun KotlinClassMetadata.findConstructor(
+            executableElement: ExecutableElement
+    ): ProtoBuf.Constructor? {
+        val (nameResolver, classProto) = data
+        val jvmSignature = executableElement.jvmMethodSignature
+        // find constructor
+        return classProto.constructorList.singleOrNull {
+            it.getJvmConstructorSignature(nameResolver, classProto.typeTable) == jvmSignature
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
new file mode 100644
index 0000000..c1c6c17
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/element_ext.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+
+package androidx.room.ext
+
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import org.jetbrains.kotlin.load.java.JvmAbi
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
+import javax.lang.model.util.SimpleTypeVisitor7
+import javax.lang.model.util.Types
+import kotlin.reflect.KClass
+
+fun Element.hasAnyOf(vararg modifiers: Modifier): Boolean {
+    return this.modifiers.any { modifiers.contains(it) }
+}
+
+fun Element.hasAnnotation(klass: KClass<out Annotation>): Boolean {
+    return MoreElements.isAnnotationPresent(this, klass.java)
+}
+
+fun Element.isNonNull() =
+        asType().kind.isPrimitive
+                || hasAnnotation(androidx.annotation.NonNull::class)
+                || hasAnnotation(org.jetbrains.annotations.NotNull::class)
+
+/**
+ * gets all members including super privates. does not handle duplicate field names!!!
+ */
+// TODO handle conflicts with super: b/35568142
+fun TypeElement.getAllFieldsIncludingPrivateSupers(processingEnvironment: ProcessingEnvironment):
+        Set<VariableElement> {
+    val myMembers = processingEnvironment.elementUtils.getAllMembers(this)
+            .filter { it.kind == ElementKind.FIELD }
+            .filter { it is VariableElement }
+            .map { it as VariableElement }
+            .toSet()
+    if (superclass.kind != TypeKind.NONE) {
+        return myMembers + MoreTypes.asTypeElement(superclass)
+                .getAllFieldsIncludingPrivateSupers(processingEnvironment)
+    } else {
+        return myMembers
+    }
+}
+
+// code below taken from dagger2
+// compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java
+private val TO_LIST_OF_TYPES = object
+    : SimpleAnnotationValueVisitor6<List<TypeMirror>, Void?>() {
+    override fun visitArray(values: MutableList<out AnnotationValue>?, p: Void?): List<TypeMirror> {
+        return values?.map {
+            val tmp = TO_TYPE.visit(it)
+            tmp
+        }?.filterNotNull() ?: emptyList()
+    }
+
+    override fun defaultAction(o: Any?, p: Void?): List<TypeMirror>? {
+        return emptyList()
+    }
+}
+
+private val TO_TYPE = object : SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
+
+    override fun visitType(t: TypeMirror, p: Void?): TypeMirror {
+        return t
+    }
+
+    override fun defaultAction(o: Any?, p: Void?): TypeMirror {
+        throw TypeNotPresentException(o!!.toString(), null)
+    }
+}
+
+fun AnnotationValue.toListOfClassTypes(): List<TypeMirror> {
+    return TO_LIST_OF_TYPES.visit(this)
+}
+
+fun AnnotationValue.toType(): TypeMirror {
+    return TO_TYPE.visit(this)
+}
+
+fun AnnotationValue.toClassType(): TypeMirror? {
+    return TO_TYPE.visit(this)
+}
+
+fun TypeMirror.isCollection(): Boolean {
+    return MoreTypes.isType(this)
+            && (MoreTypes.isTypeOf(java.util.List::class.java, this)
+            || MoreTypes.isTypeOf(java.util.Set::class.java, this))
+}
+
+fun Element.getAnnotationValue(annotation: Class<out Annotation>, fieldName: String): Any? {
+    return MoreElements.getAnnotationMirror(this, annotation)
+            .orNull()?.let {
+        AnnotationMirrors.getAnnotationValue(it, fieldName)?.value
+    }
+}
+
+private val ANNOTATION_VALUE_TO_INT_VISITOR = object : SimpleAnnotationValueVisitor6<Int?, Void>() {
+    override fun visitInt(i: Int, p: Void?): Int? {
+        return i
+    }
+}
+
+private val ANNOTATION_VALUE_TO_BOOLEAN_VISITOR = object
+    : SimpleAnnotationValueVisitor6<Boolean?, Void>() {
+    override fun visitBoolean(b: Boolean, p: Void?): Boolean? {
+        return b
+    }
+}
+
+private val ANNOTATION_VALUE_TO_STRING_VISITOR = object
+    : SimpleAnnotationValueVisitor6<String?, Void>() {
+    override fun visitString(s: String?, p: Void?): String? {
+        return s
+    }
+}
+
+private val ANNOTATION_VALUE_STRING_ARR_VISITOR = object
+    : SimpleAnnotationValueVisitor6<List<String>, Void>() {
+    override fun visitArray(vals: MutableList<out AnnotationValue>?, p: Void?): List<String> {
+        return vals?.map {
+            ANNOTATION_VALUE_TO_STRING_VISITOR.visit(it)
+        }?.filterNotNull() ?: emptyList()
+    }
+}
+
+fun AnnotationValue.getAsInt(def: Int? = null): Int? {
+    return ANNOTATION_VALUE_TO_INT_VISITOR.visit(this) ?: def
+}
+
+fun AnnotationValue.getAsString(def: String? = null): String? {
+    return ANNOTATION_VALUE_TO_STRING_VISITOR.visit(this) ?: def
+}
+
+fun AnnotationValue.getAsBoolean(def: Boolean): Boolean {
+    return ANNOTATION_VALUE_TO_BOOLEAN_VISITOR.visit(this) ?: def
+}
+
+fun AnnotationValue.getAsStringList(): List<String> {
+    return ANNOTATION_VALUE_STRING_ARR_VISITOR.visit(this)
+}
+
+// a variant of Types.isAssignable that ignores variance.
+fun Types.isAssignableWithoutVariance(from: TypeMirror, to: TypeMirror): Boolean {
+    val assignable = isAssignable(from, to)
+    if (assignable) {
+        return true
+    }
+    if (from.kind != TypeKind.DECLARED || to.kind != TypeKind.DECLARED) {
+        return false
+    }
+    val declaredFrom = MoreTypes.asDeclared(from)
+    val declaredTo = MoreTypes.asDeclared(to)
+    val fromTypeArgs = declaredFrom.typeArguments
+    val toTypeArgs = declaredTo.typeArguments
+    // no type arguments, we don't need extra checks
+    if (fromTypeArgs.isEmpty() || fromTypeArgs.size != toTypeArgs.size) {
+        return false
+    }
+    // check erasure version first, if it does not match, no reason to proceed
+    if (!isAssignable(erasure(from), erasure(to))) {
+        return false
+    }
+    // convert from args to their upper bounds if it exists
+    val fromExtendsBounds = fromTypeArgs.map {
+        it.extendsBound()
+    }
+    // if there are no upper bound conversions, return.
+    if (fromExtendsBounds.all { it == null }) {
+        return false
+    }
+    // try to move the types of the from to their upper bounds. It does not matter for the "to"
+    // because Types.isAssignable handles it as it is valid java
+    return (0 until fromTypeArgs.size).all { index ->
+        isAssignableWithoutVariance(
+                from = fromExtendsBounds[index] ?: fromTypeArgs[index],
+                to = toTypeArgs[index])
+    }
+}
+
+// converts ? in Set< ? extends Foo> to Foo
+fun TypeMirror.extendsBound(): TypeMirror? {
+    return this.accept(object : SimpleTypeVisitor7<TypeMirror?, Void?>() {
+        override fun visitWildcard(type: WildcardType, ignored: Void?): TypeMirror? {
+            return type.extendsBound
+        }
+    }, null)
+}
+
+/**
+ * Finds the default implementation method corresponding to this Kotlin interface method.
+ */
+fun Element.findKotlinDefaultImpl(typeUtils: Types): Element? {
+    fun paramsMatch(ourParams: List<VariableElement>, theirParams: List<VariableElement>): Boolean {
+        if (ourParams.size != theirParams.size - 1) {
+            return false
+        }
+        ourParams.forEachIndexed { i, variableElement ->
+            // Plus 1 to their index because their first param is a self object.
+            if (!typeUtils.isSameType(theirParams[i + 1].asType(), variableElement.asType())) {
+                return false
+            }
+        }
+        return true
+    }
+
+    val parent = this.enclosingElement as TypeElement
+    val innerClass = parent.enclosedElements.find {
+        it.kind == ElementKind.CLASS && it.simpleName.contentEquals(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)
+    } ?: return null
+    return innerClass.enclosedElements.find {
+        it.kind == ElementKind.METHOD && it.simpleName == this.simpleName
+                && paramsMatch(MoreElements.asExecutable(this).parameters,
+                MoreElements.asExecutable(it).parameters)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
new file mode 100644
index 0000000..5cf3476
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.ext
+
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+import kotlin.reflect.KClass
+
+val L = "\$L"
+val T = "\$T"
+val N = "\$N"
+val S = "\$S"
+
+fun KClass<*>.typeName() = ClassName.get(this.java)
+fun KClass<*>.arrayTypeName() = ArrayTypeName.of(typeName())
+fun TypeMirror.typeName() = TypeName.get(this)
+
+object SupportDbTypeNames {
+    val DB: ClassName = ClassName.get("androidx.sqlite.db", "SupportSQLiteDatabase")
+    val SQLITE_STMT: ClassName =
+            ClassName.get("androidx.sqlite.db", "SupportSQLiteStatement")
+    val SQLITE_OPEN_HELPER: ClassName =
+            ClassName.get("androidx.sqlite.db", "SupportSQLiteOpenHelper")
+    val SQLITE_OPEN_HELPER_CALLBACK: ClassName =
+            ClassName.get("androidx.sqlite.db", "SupportSQLiteOpenHelper.Callback")
+    val SQLITE_OPEN_HELPER_FACTORY: ClassName =
+            ClassName.get("androidx.sqlite.db", "SupportSQLiteOpenHelper.Factory")
+    val SQLITE_OPEN_HELPER_CONFIG: ClassName =
+            ClassName.get("androidx.sqlite.db", "SupportSQLiteOpenHelper.Configuration")
+    val SQLITE_OPEN_HELPER_CONFIG_BUILDER: ClassName =
+            ClassName.get("androidx.sqlite.db",
+                    "SupportSQLiteOpenHelper.Configuration.Builder")
+    val QUERY: ClassName =
+            ClassName.get("androidx.sqlite.db", "SupportSQLiteQuery")
+}
+
+object RoomTypeNames {
+    val STRING_UTIL: ClassName = ClassName.get("androidx.room.util", "StringUtil")
+    val CURSOR_CONVERTER: ClassName =
+            ClassName.get("androidx.room", "CursorConverter")
+    val ROOM: ClassName = ClassName.get("androidx.room", "Room")
+    val ROOM_DB: ClassName = ClassName.get("androidx.room", "RoomDatabase")
+    val ROOM_DB_CONFIG: ClassName = ClassName.get("androidx.room",
+            "DatabaseConfiguration")
+    val INSERTION_ADAPTER: ClassName =
+            ClassName.get("androidx.room", "EntityInsertionAdapter")
+    val DELETE_OR_UPDATE_ADAPTER: ClassName =
+            ClassName.get("androidx.room", "EntityDeletionOrUpdateAdapter")
+    val SHARED_SQLITE_STMT: ClassName =
+            ClassName.get("androidx.room", "SharedSQLiteStatement")
+    val INVALIDATION_TRACKER: ClassName =
+            ClassName.get("androidx.room", "InvalidationTracker")
+    val INVALIDATION_OBSERVER: ClassName =
+            ClassName.get("androidx.room.InvalidationTracker", "Observer")
+    val ROOM_SQL_QUERY: ClassName =
+            ClassName.get("androidx.room", "RoomSQLiteQuery")
+    val OPEN_HELPER: ClassName =
+            ClassName.get("androidx.room", "RoomOpenHelper")
+    val OPEN_HELPER_DELEGATE: ClassName =
+            ClassName.get("androidx.room", "RoomOpenHelper.Delegate")
+    val TABLE_INFO: ClassName =
+            ClassName.get("androidx.room.util", "TableInfo")
+    val TABLE_INFO_COLUMN: ClassName =
+            ClassName.get("androidx.room.util", "TableInfo.Column")
+    val TABLE_INFO_FOREIGN_KEY: ClassName =
+            ClassName.get("androidx.room.util", "TableInfo.ForeignKey")
+    val TABLE_INFO_INDEX: ClassName =
+            ClassName.get("androidx.room.util", "TableInfo.Index")
+    val LIMIT_OFFSET_DATA_SOURCE: ClassName =
+            ClassName.get("androidx.room.paging", "LimitOffsetDataSource")
+}
+
+object ArchTypeNames {
+    val APP_EXECUTOR: ClassName =
+            ClassName.get("androidx.arch.core.executor", "ArchTaskExecutor")
+}
+
+object PagingTypeNames {
+    val DATA_SOURCE: ClassName =
+            ClassName.get("androidx.paging", "DataSource")
+    val POSITIONAL_DATA_SOURCE: ClassName =
+            ClassName.get("androidx.paging", "PositionalDataSource")
+    val DATA_SOURCE_FACTORY: ClassName =
+            ClassName.get("androidx.paging", "DataSource.Factory")
+}
+
+object LifecyclesTypeNames {
+    val LIVE_DATA: ClassName = ClassName.get("androidx.lifecycle", "LiveData")
+    val COMPUTABLE_LIVE_DATA: ClassName = ClassName.get("androidx.lifecycle",
+            "ComputableLiveData")
+}
+
+object AndroidTypeNames {
+    val CURSOR: ClassName = ClassName.get("android.database", "Cursor")
+    val ARRAY_MAP: ClassName = ClassName.get("androidx.collection", "ArrayMap")
+    val BUILD: ClassName = ClassName.get("android.os", "Build")
+}
+
+object CommonTypeNames {
+    val LIST = ClassName.get("java.util", "List")
+    val SET = ClassName.get("java.util", "Set")
+    val STRING = ClassName.get("java.lang", "String")
+    val INTEGER = ClassName.get("java.lang", "Integer")
+    val OPTIONAL = ClassName.get("java.util", "Optional")
+}
+
+object GuavaBaseTypeNames {
+    val OPTIONAL = ClassName.get("com.google.common.base", "Optional")
+}
+
+object GuavaUtilConcurrentTypeNames {
+    val LISTENABLE_FUTURE = ClassName.get("com.google.common.util.concurrent", "ListenableFuture")
+}
+
+object RxJava2TypeNames {
+    val FLOWABLE = ClassName.get("io.reactivex", "Flowable")
+    val MAYBE = ClassName.get("io.reactivex", "Maybe")
+    val SINGLE = ClassName.get("io.reactivex", "Single")
+}
+
+object ReactiveStreamsTypeNames {
+    val PUBLISHER = ClassName.get("org.reactivestreams", "Publisher")
+}
+
+object RoomGuavaTypeNames {
+    val GUAVA_ROOM = ClassName.get("androidx.room.guava", "GuavaRoom")
+}
+
+object RoomRxJava2TypeNames {
+    val RX_ROOM = ClassName.get("androidx.room", "RxRoom")
+    val RX_EMPTY_RESULT_SET_EXCEPTION = ClassName.get("androidx.room",
+            "EmptyResultSetException")
+}
+
+fun TypeName.defaultValue(): String {
+    return if (!isPrimitive) {
+        "null"
+    } else if (this == TypeName.BOOLEAN) {
+        "false"
+    } else {
+        "0"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/string_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/string_ext.kt
similarity index 100%
rename from room/compiler/src/main/kotlin/android/arch/persistence/room/ext/string_ext.kt
rename to room/compiler/src/main/kotlin/androidx/room/ext/string_ext.kt
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/ext/type_mirror_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/type_mirror_ext.kt
similarity index 100%
rename from room/compiler/src/main/kotlin/android/arch/persistence/room/ext/type_mirror_ext.kt
rename to room/compiler/src/main/kotlin/androidx/room/ext/type_mirror_ext.kt
diff --git a/room/compiler/src/main/kotlin/androidx/room/log/RLog.kt b/room/compiler/src/main/kotlin/androidx/room/log/RLog.kt
new file mode 100644
index 0000000..22dd921
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/log/RLog.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("unused")
+
+package androidx.room.log
+
+import androidx.room.vo.Warning
+import java.util.UnknownFormatConversionException
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.Element
+import javax.tools.Diagnostic
+import javax.tools.Diagnostic.Kind.ERROR
+import javax.tools.Diagnostic.Kind.NOTE
+import javax.tools.Diagnostic.Kind.WARNING
+
+class RLog(val messager: Messager, val suppressedWarnings: Set<Warning>,
+           val defaultElement: Element?) {
+    private fun String.safeFormat(vararg args: Any): String {
+        try {
+            return format(args)
+        } catch (ex: UnknownFormatConversionException) {
+            // the input string might be from random source in which case we rather print the
+            // msg as is instead of crashing while reporting an error.
+            return this
+        }
+    }
+
+    fun d(element: Element, msg: String, vararg args: Any) {
+        messager.printMessage(NOTE, msg.safeFormat(args), element)
+    }
+
+    fun d(msg: String, vararg args: Any) {
+        messager.printMessage(NOTE, msg.safeFormat(args))
+    }
+
+    fun e(element: Element, msg: String, vararg args: Any) {
+        messager.printMessage(ERROR, msg.safeFormat(args), element)
+    }
+
+    fun e(msg: String, vararg args: Any) {
+        messager.printMessage(ERROR, msg.safeFormat(args), defaultElement)
+    }
+
+    fun w(warning: Warning, element: Element? = null, msg: String, vararg args: Any) {
+        if (suppressedWarnings.contains(warning)) {
+            return
+        }
+        messager.printMessage(WARNING, msg.safeFormat(args),
+                element ?: defaultElement)
+    }
+
+    fun w(warning: Warning, msg: String, vararg args: Any) {
+        if (suppressedWarnings.contains(warning)) {
+            return
+        }
+        messager.printMessage(WARNING, msg.safeFormat(args), defaultElement)
+    }
+
+    interface Messager {
+        fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element? = null)
+    }
+
+    class ProcessingEnvMessager(val processingEnv: ProcessingEnvironment) : Messager {
+        override fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element?) {
+            processingEnv.messager.printMessage(kind, msg, element)
+        }
+    }
+
+    class CollectingMessager : Messager {
+        private val messages = mutableMapOf<Diagnostic.Kind, MutableList<Pair<String, Element?>>> ()
+        override fun printMessage(kind: Diagnostic.Kind, msg: String, element: Element?) {
+            messages.getOrPut(kind, {
+                arrayListOf<Pair<String, Element?>>()
+            }).add(Pair(msg, element))
+        }
+
+        fun hasErrors() = messages.containsKey(Diagnostic.Kind.ERROR)
+
+        fun writeTo(env: ProcessingEnvironment) {
+            messages.forEach { pair ->
+                val kind = pair.key
+                pair.value.forEach {
+                    env.messager.printMessage(kind, it.first, it.second)
+                }
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/parser/ParsedQuery.kt b/room/compiler/src/main/kotlin/androidx/room/parser/ParsedQuery.kt
new file mode 100644
index 0000000..b349012
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/parser/ParsedQuery.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.parser
+
+import androidx.room.parser.SectionType.BIND_VAR
+import androidx.room.parser.SectionType.NEWLINE
+import androidx.room.parser.SectionType.TEXT
+import androidx.room.verifier.QueryResultInfo
+import org.antlr.v4.runtime.tree.TerminalNode
+
+enum class SectionType {
+    BIND_VAR,
+    TEXT,
+    NEWLINE
+}
+
+data class Section(val text: String, val type: SectionType) {
+    companion object {
+        fun text(text: String) = Section(text, SectionType.TEXT)
+        fun newline() = Section("", SectionType.NEWLINE)
+        fun bindVar(text: String) = Section(text, SectionType.BIND_VAR)
+    }
+}
+
+data class Table(val name: String, val alias: String)
+
+data class ParsedQuery(
+        val original: String,
+        val type: QueryType,
+        val inputs: List<TerminalNode>,
+        // pairs of table name and alias,
+        val tables: Set<Table>,
+        val syntaxErrors: List<String>,
+        val runtimeQueryPlaceholder: Boolean) {
+    companion object {
+        val STARTS_WITH_NUMBER = "^\\?[0-9]".toRegex()
+        val MISSING = ParsedQuery("missing query", QueryType.UNKNOWN, emptyList(), emptySet(),
+                emptyList(), false)
+    }
+
+    /**
+     * Optional data that might be assigned when the query is parsed inside an annotation processor.
+     * User may turn this off or it might be disabled for any reason so generated code should
+     * always handle not having it.
+     */
+    var resultInfo: QueryResultInfo? = null
+
+    val sections by lazy {
+        val lines = original.lines()
+        val inputsByLine = inputs.groupBy { it.symbol.line }
+        val sections = arrayListOf<Section>()
+        lines.forEachIndexed { index, line ->
+            var charInLine = 0
+            inputsByLine[index + 1]?.forEach { bindVar ->
+                if (charInLine < bindVar.symbol.charPositionInLine) {
+                    sections.add(Section.text(line.substring(charInLine,
+                            bindVar.symbol.charPositionInLine)))
+                }
+                sections.add(Section.bindVar(bindVar.text))
+                charInLine = bindVar.symbol.charPositionInLine + bindVar.symbol.text.length
+            }
+            if (charInLine < line.length) {
+                sections.add(Section.text(line.substring(charInLine)))
+            }
+            if (index + 1 < lines.size) {
+                sections.add(Section.newline())
+            }
+        }
+        sections
+    }
+
+    val bindSections by lazy { sections.filter { it.type == BIND_VAR } }
+
+    private fun unnamedVariableErrors(): List<String> {
+        val anonymousBindError = if (inputs.any { it.text == "?" }) {
+            arrayListOf(ParserErrors.ANONYMOUS_BIND_ARGUMENT)
+        } else {
+            emptyList<String>()
+        }
+        return anonymousBindError + inputs.filter {
+            it.text.matches(STARTS_WITH_NUMBER)
+        }.map {
+            ParserErrors.cannotUseVariableIndices(it.text, it.symbol.charPositionInLine)
+        }
+    }
+
+    private fun unknownQueryTypeErrors(): List<String> {
+        return if (QueryType.SUPPORTED.contains(type)) {
+            emptyList()
+        } else {
+            listOf(ParserErrors.invalidQueryType(type))
+        }
+    }
+
+    val errors by lazy {
+        if (syntaxErrors.isNotEmpty()) {
+            // if there is a syntax error, don't report others since they might be misleading.
+            syntaxErrors
+        } else {
+            unnamedVariableErrors() + unknownQueryTypeErrors()
+        }
+    }
+
+    val queryWithReplacedBindParams by lazy {
+        sections.joinToString("") {
+            when (it.type) {
+                TEXT -> it.text
+                BIND_VAR -> "?"
+                NEWLINE -> "\n"
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/parser/ParserErrors.kt b/room/compiler/src/main/kotlin/androidx/room/parser/ParserErrors.kt
new file mode 100644
index 0000000..9c9d921
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/parser/ParserErrors.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.parser
+
+object ParserErrors {
+    val ANONYMOUS_BIND_ARGUMENT = "Room does not support ? as bind parameters. You must use" +
+            " named bind arguments (e..g :argName)"
+
+    val NOT_ONE_QUERY = "Must have exactly 1 query in @Query value"
+
+    fun invalidQueryType(type: QueryType): String {
+        return "$type query type is not supported yet. You can use:" +
+                QueryType.SUPPORTED.joinToString(", ") { it.name }
+    }
+
+    fun cannotUseVariableIndices(name: String, position: Int) = "Cannot use variable indices." +
+            " Use named parameters instead (e.g. WHERE name LIKE :nameArg and lastName LIKE " +
+            ":lastName). Problem: $name at $position"
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
new file mode 100644
index 0000000..45b58ae
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.parser
+
+import androidx.room.ColumnInfo
+import org.antlr.v4.runtime.ANTLRInputStream
+import org.antlr.v4.runtime.BaseErrorListener
+import org.antlr.v4.runtime.CommonTokenStream
+import org.antlr.v4.runtime.RecognitionException
+import org.antlr.v4.runtime.Recognizer
+import org.antlr.v4.runtime.tree.ParseTree
+import org.antlr.v4.runtime.tree.TerminalNode
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@Suppress("FunctionName")
+class QueryVisitor(
+        private val original: String,
+        private val syntaxErrors: ArrayList<String>,
+        statement: ParseTree,
+        private val forRuntimeQuery: Boolean
+) : SQLiteBaseVisitor<Void?>() {
+    private val bindingExpressions = arrayListOf<TerminalNode>()
+    // table name alias mappings
+    private val tableNames = mutableSetOf<Table>()
+    private val withClauseNames = mutableSetOf<String>()
+    private val queryType: QueryType
+
+    init {
+        queryType = (0 until statement.childCount).map {
+            findQueryType(statement.getChild(it))
+        }.filterNot { it == QueryType.UNKNOWN }.firstOrNull() ?: QueryType.UNKNOWN
+
+        statement.accept(this)
+    }
+
+    private fun findQueryType(statement: ParseTree): QueryType {
+        return when (statement) {
+            is SQLiteParser.Factored_select_stmtContext,
+            is SQLiteParser.Compound_select_stmtContext,
+            is SQLiteParser.Select_stmtContext,
+            is SQLiteParser.Simple_select_stmtContext ->
+                QueryType.SELECT
+
+            is SQLiteParser.Delete_stmt_limitedContext,
+            is SQLiteParser.Delete_stmtContext ->
+                QueryType.DELETE
+
+            is SQLiteParser.Insert_stmtContext ->
+                QueryType.INSERT
+            is SQLiteParser.Update_stmtContext,
+            is SQLiteParser.Update_stmt_limitedContext ->
+                QueryType.UPDATE
+            is TerminalNode -> when (statement.text) {
+                "EXPLAIN" -> QueryType.EXPLAIN
+                else -> QueryType.UNKNOWN
+            }
+            else -> QueryType.UNKNOWN
+        }
+    }
+
+    override fun visitExpr(ctx: SQLiteParser.ExprContext): Void? {
+        val bindParameter = ctx.BIND_PARAMETER()
+        if (bindParameter != null) {
+            bindingExpressions.add(bindParameter)
+        }
+        return super.visitExpr(ctx)
+    }
+
+    fun createParsedQuery(): ParsedQuery {
+        return ParsedQuery(
+                original = original,
+                type = queryType,
+                inputs = bindingExpressions.sortedBy { it.sourceInterval.a },
+                tables = tableNames,
+                syntaxErrors = syntaxErrors,
+                runtimeQueryPlaceholder = forRuntimeQuery)
+    }
+
+    override fun visitCommon_table_expression(
+            ctx: SQLiteParser.Common_table_expressionContext): Void? {
+        val tableName = ctx.table_name()?.text
+        if (tableName != null) {
+            withClauseNames.add(unescapeIdentifier(tableName))
+        }
+        return super.visitCommon_table_expression(ctx)
+    }
+
+    override fun visitTable_or_subquery(ctx: SQLiteParser.Table_or_subqueryContext): Void? {
+        val tableName = ctx.table_name()?.text
+        if (tableName != null) {
+            val tableAlias = ctx.table_alias()?.text
+            if (tableName !in withClauseNames) {
+                tableNames.add(Table(
+                        unescapeIdentifier(tableName),
+                        unescapeIdentifier(tableAlias ?: tableName)))
+            }
+        }
+        return super.visitTable_or_subquery(ctx)
+    }
+
+    private fun unescapeIdentifier(text: String): String {
+        val trimmed = text.trim()
+        ESCAPE_LITERALS.forEach {
+            if (trimmed.startsWith(it) && trimmed.endsWith(it)) {
+                return unescapeIdentifier(trimmed.substring(1, trimmed.length - 1))
+            }
+        }
+        return trimmed
+    }
+
+    companion object {
+        private val ESCAPE_LITERALS = listOf("\"", "'", "`")
+    }
+}
+
+class SqlParser {
+    companion object {
+        private val INVALID_IDENTIFIER_CHARS = arrayOf('`', '\"')
+        fun parse(input: String): ParsedQuery {
+            val inputStream = ANTLRInputStream(input)
+            val lexer = SQLiteLexer(inputStream)
+            val tokenStream = CommonTokenStream(lexer)
+            val parser = SQLiteParser(tokenStream)
+            val syntaxErrors = arrayListOf<String>()
+            parser.addErrorListener(object : BaseErrorListener() {
+                override fun syntaxError(
+                        recognizer: Recognizer<*, *>, offendingSymbol: Any,
+                        line: Int, charPositionInLine: Int, msg: String,
+                        e: RecognitionException?) {
+                    syntaxErrors.add(msg)
+                }
+            })
+            try {
+                val parsed = parser.parse()
+                val statementList = parsed.sql_stmt_list()
+                if (statementList.isEmpty()) {
+                    syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
+                    return ParsedQuery(input, QueryType.UNKNOWN, emptyList(), emptySet(),
+                            listOf(ParserErrors.NOT_ONE_QUERY), false)
+                }
+                val statements = statementList.first().children
+                        .filter { it is SQLiteParser.Sql_stmtContext }
+                if (statements.size != 1) {
+                    syntaxErrors.add(ParserErrors.NOT_ONE_QUERY)
+                }
+                val statement = statements.first()
+                return QueryVisitor(
+                        original = input,
+                        syntaxErrors = syntaxErrors,
+                        statement = statement,
+                        forRuntimeQuery = false).createParsedQuery()
+            } catch (antlrError: RuntimeException) {
+                return ParsedQuery(input, QueryType.UNKNOWN, emptyList(), emptySet(),
+                        listOf("unknown error while parsing $input : ${antlrError.message}"),
+                        false)
+            }
+        }
+
+        fun isValidIdentifier(input: String): Boolean =
+                input.isNotBlank() && INVALID_IDENTIFIER_CHARS.none { input.contains(it) }
+
+        /**
+         * creates a dummy select query for raw queries that queries the given list of tables.
+         */
+        fun rawQueryForTables(tableNames: Set<String>): ParsedQuery {
+            return ParsedQuery(
+                    original = "raw query",
+                    type = QueryType.UNKNOWN,
+                    inputs = emptyList(),
+                    tables = tableNames.map { Table(name = it, alias = it) }.toSet(),
+                    syntaxErrors = emptyList(),
+                    runtimeQueryPlaceholder = true
+            )
+        }
+    }
+}
+
+enum class QueryType {
+    UNKNOWN,
+    SELECT,
+    DELETE,
+    UPDATE,
+    EXPLAIN,
+    INSERT;
+
+    companion object {
+        // IF you change this, don't forget to update @Query documentation.
+        val SUPPORTED = hashSetOf(SELECT, DELETE, UPDATE)
+    }
+}
+
+enum class SQLTypeAffinity {
+    NULL,
+    TEXT,
+    INTEGER,
+    REAL,
+    BLOB;
+
+    fun getTypeMirrors(env: ProcessingEnvironment): List<TypeMirror>? {
+        val typeUtils = env.typeUtils
+        return when (this) {
+            TEXT -> listOf(env.elementUtils.getTypeElement("java.lang.String").asType())
+            INTEGER -> withBoxedTypes(env, TypeKind.INT, TypeKind.BYTE, TypeKind.CHAR,
+                    TypeKind.LONG, TypeKind.SHORT)
+            REAL -> withBoxedTypes(env, TypeKind.DOUBLE, TypeKind.FLOAT)
+            BLOB -> listOf(typeUtils.getArrayType(
+                    typeUtils.getPrimitiveType(TypeKind.BYTE)))
+            else -> emptyList()
+        }
+    }
+
+    private fun withBoxedTypes(env: ProcessingEnvironment, vararg primitives: TypeKind):
+            List<TypeMirror> {
+        return primitives.flatMap {
+            val primitiveType = env.typeUtils.getPrimitiveType(it)
+            listOf(primitiveType, env.typeUtils.boxedClass(primitiveType).asType())
+        }
+    }
+
+    companion object {
+        // converts from ColumnInfo#SQLiteTypeAffinity
+        fun fromAnnotationValue(value: Int): SQLTypeAffinity? {
+            return when (value) {
+                ColumnInfo.BLOB -> BLOB
+                ColumnInfo.INTEGER -> INTEGER
+                ColumnInfo.REAL -> REAL
+                ColumnInfo.TEXT -> TEXT
+                else -> null
+            }
+        }
+    }
+}
+
+enum class Collate {
+    BINARY,
+    NOCASE,
+    RTRIM,
+    LOCALIZED,
+    UNICODE;
+
+    companion object {
+        fun fromAnnotationValue(value: Int): Collate? {
+            return when (value) {
+                ColumnInfo.BINARY -> BINARY
+                ColumnInfo.NOCASE -> NOCASE
+                ColumnInfo.RTRIM -> RTRIM
+                ColumnInfo.LOCALIZED -> LOCALIZED
+                ColumnInfo.UNICODE -> UNICODE
+                else -> null
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt b/room/compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
new file mode 100644
index 0000000..2753229
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.preconditions
+
+import androidx.room.ext.hasAnnotation
+import androidx.room.log.RLog
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
+import javax.lang.model.element.Element
+import kotlin.reflect.KClass
+
+/**
+ * Similar to preconditions but element bound and just logs the error instead of throwing an
+ * exception.
+ * <p>
+ * It is important for processing to continue when some errors happen so that we can generate as
+ * much code as possible, leaving only the errors in javac output.
+ */
+class Checks(private val logger: RLog) {
+
+    fun check(predicate: Boolean, element: Element, errorMsg: String, vararg args: Any): Boolean {
+        if (!predicate) {
+            logger.e(element, errorMsg, args)
+        }
+        return predicate
+    }
+
+    fun hasAnnotation(element: Element, annotation: KClass<out Annotation>, errorMsg: String,
+                      vararg args: Any): Boolean {
+        return if (!element.hasAnnotation(annotation)) {
+            logger.e(element, errorMsg, args)
+            false
+        } else {
+            true
+        }
+    }
+
+    fun notUnbound(typeName: TypeName, element: Element, errorMsg: String,
+                   vararg args: Any): Boolean {
+        // TODO support bounds cases like <T extends Foo> T bar()
+        val failed = check(typeName !is TypeVariableName, element, errorMsg, args)
+        if (typeName is ParameterizedTypeName) {
+            val nestedFailure = typeName.typeArguments
+                    .map { notUnbound(it, element, errorMsg, args) }
+                    .any { it == true }
+            return !(failed || nestedFailure)
+        }
+        return !failed
+    }
+
+    fun notBlank(value: String?, element: Element, msg: String, vararg args: Any): Boolean {
+        return check(value != null && value.isNotBlank(), element, msg, args)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/Context.kt b/room/compiler/src/main/kotlin/androidx/room/processor/Context.kt
new file mode 100644
index 0000000..d3e61b2
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/Context.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.log.RLog
+import androidx.room.preconditions.Checks
+import androidx.room.processor.cache.Cache
+import androidx.room.solver.TypeAdapterStore
+import androidx.room.verifier.DatabaseVerifier
+import java.io.File
+import java.util.LinkedHashSet
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+
+class Context private constructor(
+        val processingEnv: ProcessingEnvironment,
+        val logger: RLog,
+        private val typeConverters: CustomConverterProcessor.ProcessResult,
+        private val inheritedAdapterStore: TypeAdapterStore?,
+        val cache: Cache) {
+    val checker: Checks = Checks(logger)
+    val COMMON_TYPES: Context.CommonTypes = Context.CommonTypes(processingEnv)
+
+    val typeAdapterStore by lazy {
+        if (inheritedAdapterStore != null) {
+            TypeAdapterStore.copy(this, inheritedAdapterStore)
+        } else {
+            TypeAdapterStore.create(this, typeConverters.converters)
+        }
+    }
+
+    // set when database and its entities are processed.
+    var databaseVerifier: DatabaseVerifier? = null
+
+    companion object {
+        val ARG_OPTIONS by lazy {
+            ProcessorOptions.values().map { it.argName }
+        }
+    }
+
+    constructor(processingEnv: ProcessingEnvironment) : this(
+            processingEnv = processingEnv,
+            logger = RLog(RLog.ProcessingEnvMessager(processingEnv), emptySet(), null),
+            typeConverters = CustomConverterProcessor.ProcessResult.EMPTY,
+            inheritedAdapterStore = null,
+            cache = Cache(null, LinkedHashSet(), emptySet())) {
+    }
+
+    class CommonTypes(val processingEnv: ProcessingEnvironment) {
+        val STRING: TypeMirror by lazy {
+            processingEnv.elementUtils.getTypeElement("java.lang.String").asType()
+        }
+    }
+
+    val schemaOutFolder by lazy {
+        val arg = processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
+        if (arg?.isNotEmpty() ?: false) {
+            File(arg)
+        } else {
+            null
+        }
+    }
+
+    fun <T> collectLogs(handler: (Context) -> T): Pair<T, RLog.CollectingMessager> {
+        val collector = RLog.CollectingMessager()
+        val subContext = Context(processingEnv = processingEnv,
+                logger = RLog(collector, logger.suppressedWarnings, logger.defaultElement),
+                typeConverters = this.typeConverters,
+                inheritedAdapterStore = typeAdapterStore,
+                cache = cache)
+        subContext.databaseVerifier = databaseVerifier
+        val result = handler(subContext)
+        return Pair(result, collector)
+    }
+
+    fun fork(element: Element): Context {
+        val suppressedWarnings = SuppressWarningProcessor.getSuppressedWarnings(element)
+        val processConvertersResult = CustomConverterProcessor.findConverters(this, element)
+        val canReUseAdapterStore = processConvertersResult.classes.isEmpty()
+        // order here is important since the sub context should give priority to new converters.
+        val subTypeConverters = if (canReUseAdapterStore) {
+            this.typeConverters
+        } else {
+            processConvertersResult + this.typeConverters
+        }
+        val subSuppressedWarnings = suppressedWarnings + logger.suppressedWarnings
+        val subCache = Cache(cache, subTypeConverters.classes, subSuppressedWarnings)
+        val subContext = Context(
+                processingEnv = processingEnv,
+                logger = RLog(logger.messager, subSuppressedWarnings, element),
+                typeConverters = subTypeConverters,
+                inheritedAdapterStore = if (canReUseAdapterStore) typeAdapterStore else null,
+                cache = subCache)
+        subContext.databaseVerifier = databaseVerifier
+        return subContext
+    }
+
+    enum class ProcessorOptions(val argName: String) {
+        OPTION_SCHEMA_FOLDER("room.schemaLocation")
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
new file mode 100644
index 0000000..ba8658c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
@@ -0,0 +1,151 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.TypeConverter
+import androidx.room.TypeConverters
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.hasAnyOf
+import androidx.room.ext.toListOfClassTypes
+import androidx.room.ext.typeName
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_BAD_RETURN_TYPE
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
+import androidx.room.processor.ProcessorErrors
+        .TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_RECEIVE_1_PARAM
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import androidx.room.solver.types.CustomTypeConverterWrapper
+import androidx.room.vo.CustomTypeConverter
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import java.util.LinkedHashSet
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
+
+/**
+ * Processes classes that are referenced in TypeConverters annotations.
+ */
+class CustomConverterProcessor(val context: Context, val element: TypeElement) {
+    companion object {
+        private val INVALID_RETURN_TYPES = setOf(TypeKind.ERROR, TypeKind.VOID, TypeKind.NONE)
+        fun findConverters(context: Context, element: Element): ProcessResult {
+            val annotation = MoreElements.getAnnotationMirror(element,
+                    TypeConverters::class.java).orNull()
+            return annotation?.let {
+                val classes = AnnotationMirrors.getAnnotationValue(annotation, "value")
+                        ?.toListOfClassTypes()
+                        ?.filter {
+                            MoreTypes.isType(it)
+                        }?.mapTo(LinkedHashSet(), { it }) ?: LinkedHashSet<TypeMirror>()
+                val converters = classes
+                        .flatMap {
+                            CustomConverterProcessor(context, MoreTypes.asTypeElement(it))
+                                    .process()
+                        }
+                converters.let {
+                    reportDuplicates(context, converters)
+                }
+                ProcessResult(classes, converters.map(::CustomTypeConverterWrapper))
+            } ?: ProcessResult.EMPTY
+        }
+
+        fun reportDuplicates(context: Context, converters: List<CustomTypeConverter>) {
+            val groupedByFrom = converters.groupBy { it.from.typeName() }
+            groupedByFrom.forEach {
+                it.value.groupBy { it.to.typeName() }.forEach {
+                    if (it.value.size > 1) {
+                        it.value.forEach { converter ->
+                            context.logger.e(converter.method, ProcessorErrors
+                                    .duplicateTypeConverters(it.value.minus(converter)))
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun process(): List<CustomTypeConverter> {
+        // using element utils instead of MoreElements to include statics.
+        val methods = ElementFilter
+                .methodsIn(context.processingEnv.elementUtils.getAllMembers(element))
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        val converterMethods = methods.filter {
+            it.hasAnnotation(TypeConverter::class)
+        }
+        context.checker.check(converterMethods.isNotEmpty(), element, TYPE_CONVERTER_EMPTY_CLASS)
+        val allStatic = converterMethods.all { it.modifiers.contains(Modifier.STATIC) }
+        val constructors = ElementFilter.constructorsIn(
+                context.processingEnv.elementUtils.getAllMembers(element))
+        context.checker.check(allStatic || constructors.isEmpty() || constructors.any {
+            it.parameters.isEmpty()
+        }, element, TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+        return converterMethods.map {
+            processMethod(declaredType, it)
+        }.filterNotNull()
+    }
+
+    private fun processMethod(
+            container: DeclaredType, methodElement: ExecutableElement): CustomTypeConverter? {
+        val asMember = context.processingEnv.typeUtils.asMemberOf(container, methodElement)
+        val executableType = MoreTypes.asExecutable(asMember)
+        val returnType = executableType.returnType
+        val invalidReturnType = INVALID_RETURN_TYPES.contains(returnType.kind)
+        context.checker.check(methodElement.hasAnyOf(Modifier.PUBLIC), methodElement,
+                TYPE_CONVERTER_MUST_BE_PUBLIC)
+        if (invalidReturnType) {
+            context.logger.e(methodElement, TYPE_CONVERTER_BAD_RETURN_TYPE)
+            return null
+        }
+        val returnTypeName = returnType.typeName()
+        context.checker.notUnbound(returnTypeName, methodElement,
+                TYPE_CONVERTER_UNBOUND_GENERIC)
+        val params = methodElement.parameters
+        if (params.size != 1) {
+            context.logger.e(methodElement, TYPE_CONVERTER_MUST_RECEIVE_1_PARAM)
+            return null
+        }
+        val param = params.map {
+            MoreTypes.asMemberOf(context.processingEnv.typeUtils, container, it)
+        }.first()
+        context.checker.notUnbound(param.typeName(), params[0], TYPE_CONVERTER_UNBOUND_GENERIC)
+        return CustomTypeConverter(container, methodElement, param, returnType)
+    }
+
+    /**
+     * Order of classes is important hence they are a LinkedHashSet not a set.
+     */
+    open class ProcessResult(
+            val classes: LinkedHashSet<TypeMirror>,
+            val converters: List<CustomTypeConverterWrapper>
+    ) {
+        object EMPTY : ProcessResult(LinkedHashSet(), emptyList())
+        operator fun plus(other: ProcessResult): ProcessResult {
+            val newClasses = LinkedHashSet<TypeMirror>()
+            newClasses.addAll(classes)
+            newClasses.addAll(other.classes)
+            return ProcessResult(newClasses, converters + other.converters)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
new file mode 100644
index 0000000..cf8ab0d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.RawQuery
+import androidx.room.SkipQueryVerification
+import androidx.room.Transaction
+import androidx.room.Update
+import androidx.room.ext.findKotlinDefaultImpl
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.hasAnyOf
+import androidx.room.ext.typeName
+import androidx.room.verifier.DatabaseVerifier
+import androidx.room.vo.Dao
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier.ABSTRACT
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+class DaoProcessor(baseContext: Context, val element: TypeElement, val dbType: DeclaredType,
+                   val dbVerifier: DatabaseVerifier?) {
+    val context = baseContext.fork(element)
+
+    companion object {
+        val PROCESSED_ANNOTATIONS = listOf(Insert::class, Delete::class, Query::class,
+                Update::class, RawQuery::class)
+    }
+
+    fun process(): Dao {
+        context.checker.hasAnnotation(element, androidx.room.Dao::class,
+                ProcessorErrors.DAO_MUST_BE_ANNOTATED_WITH_DAO)
+        context.checker.check(element.hasAnyOf(ABSTRACT) || element.kind == ElementKind.INTERFACE,
+                element, ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
+
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
+        val methods = allMembers
+            .filter {
+                it.hasAnyOf(ABSTRACT) && it.kind == ElementKind.METHOD
+                        && it.findKotlinDefaultImpl(context.processingEnv.typeUtils) == null
+            }.map {
+                MoreElements.asExecutable(it)
+            }.groupBy { method ->
+                context.checker.check(
+                        PROCESSED_ANNOTATIONS.count { method.hasAnnotation(it) } == 1, method,
+                        ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD
+                )
+                if (method.hasAnnotation(Query::class)) {
+                    Query::class
+                } else if (method.hasAnnotation(Insert::class)) {
+                    Insert::class
+                } else if (method.hasAnnotation(Delete::class)) {
+                    Delete::class
+                } else if (method.hasAnnotation(Update::class)) {
+                    Update::class
+                } else if (method.hasAnnotation(RawQuery::class)) {
+                    RawQuery::class
+                } else {
+                    Any::class
+                }
+            }
+        val processorVerifier = if (element.hasAnnotation(SkipQueryVerification::class) ||
+                element.hasAnnotation(RawQuery::class)) {
+            null
+        } else {
+            dbVerifier
+        }
+
+        val queryMethods = methods[Query::class]?.map {
+            QueryMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it,
+                    dbVerifier = processorVerifier).process()
+        } ?: emptyList()
+
+        val rawQueryMethods = methods[RawQuery::class]?.map {
+            RawQueryMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it
+            ).process()
+        } ?: emptyList()
+
+        val insertionMethods = methods[Insert::class]?.map {
+            InsertionMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it).process()
+        } ?: emptyList()
+
+        val deletionMethods = methods[Delete::class]?.map {
+            DeletionMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it).process()
+        } ?: emptyList()
+
+        val updateMethods = methods[Update::class]?.map {
+            UpdateMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = it).process()
+        } ?: emptyList()
+
+        val transactionMethods = allMembers.filter { member ->
+            member.hasAnnotation(Transaction::class)
+                    && member.kind == ElementKind.METHOD
+                    && PROCESSED_ANNOTATIONS.none { member.hasAnnotation(it) }
+        }.map {
+            TransactionMethodProcessor(
+                    baseContext = context,
+                    containing = declaredType,
+                    executableElement = MoreElements.asExecutable(it)).process()
+        }
+
+        val constructors = allMembers
+                .filter { it.kind == ElementKind.CONSTRUCTOR }
+                .map { MoreElements.asExecutable(it) }
+        val typeUtils = context.processingEnv.typeUtils
+        val goodConstructor = constructors
+                .filter {
+                    it.parameters.size == 1
+                            && typeUtils.isAssignable(dbType, it.parameters[0].asType())
+                }
+                .firstOrNull()
+        val constructorParamType = if (goodConstructor != null) {
+            goodConstructor.parameters[0].asType().typeName()
+        } else {
+            validateEmptyConstructor(constructors)
+            null
+        }
+
+        context.checker.check(methods[Any::class] == null, element,
+                ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION)
+
+        val type = TypeName.get(declaredType)
+        context.checker.notUnbound(type, element,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES)
+
+        return Dao(element = element,
+                type = declaredType,
+                queryMethods = queryMethods,
+                rawQueryMethods = rawQueryMethods,
+                insertionMethods = insertionMethods,
+                deletionMethods = deletionMethods,
+                updateMethods = updateMethods,
+                transactionMethods = transactionMethods,
+                constructorParamType = constructorParamType)
+    }
+
+    private fun validateEmptyConstructor(constructors: List<ExecutableElement>) {
+        if (constructors.isNotEmpty() && constructors.all { it.parameters.isNotEmpty() }) {
+            context.logger.e(element, ProcessorErrors.daoMustHaveMatchingConstructor(
+                    element.toString(), dbType.toString()))
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
new file mode 100644
index 0000000..f4219ff
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.SkipQueryVerification
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.getAsBoolean
+import androidx.room.ext.getAsInt
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.hasAnyOf
+import androidx.room.ext.toListOfClassTypes
+import androidx.room.verifier.DatabaseVerifier
+import androidx.room.vo.Dao
+import androidx.room.vo.DaoMethod
+import androidx.room.vo.Database
+import androidx.room.vo.Entity
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeMirror
+
+class DatabaseProcessor(baseContext: Context, val element: TypeElement) {
+    val context = baseContext.fork(element)
+
+    val baseClassElement: TypeMirror by lazy {
+        context.processingEnv.elementUtils.getTypeElement(
+                RoomTypeNames.ROOM_DB.packageName() + "." + RoomTypeNames.ROOM_DB.simpleName())
+                .asType()
+    }
+
+    fun process(): Database {
+        try {
+            return doProcess()
+        } finally {
+            context.databaseVerifier?.closeConnection(context)
+        }
+    }
+
+    private fun doProcess(): Database {
+        val dbAnnotation = MoreElements
+                .getAnnotationMirror(element, androidx.room.Database::class.java)
+                .orNull()
+        val entities = processEntities(dbAnnotation, element)
+        validateUniqueTableNames(element, entities)
+        validateForeignKeys(element, entities)
+
+        val extendsRoomDb = context.processingEnv.typeUtils.isAssignable(
+                MoreElements.asType(element).asType(), baseClassElement)
+        context.checker.check(extendsRoomDb, element, ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
+
+        val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
+
+        val dbVerifier = if (element.hasAnnotation(SkipQueryVerification::class)) {
+            null
+        } else {
+            DatabaseVerifier.create(context, element, entities)
+        }
+        context.databaseVerifier = dbVerifier
+
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        val daoMethods = allMembers.filter {
+            it.hasAnyOf(Modifier.ABSTRACT) && it.kind == ElementKind.METHOD
+        }.filterNot {
+            // remove methods that belong to room
+            val containing = it.enclosingElement
+            MoreElements.isType(containing) &&
+                    TypeName.get(containing.asType()) == RoomTypeNames.ROOM_DB
+        }.map {
+            val executable = MoreElements.asExecutable(it)
+            // TODO when we add support for non Dao return types (e.g. database), this code needs
+            // to change
+            val daoType = MoreTypes.asTypeElement(executable.returnType)
+            val dao = DaoProcessor(context, daoType, declaredType, dbVerifier).process()
+            DaoMethod(executable, executable.simpleName.toString(), dao)
+        }
+        validateUniqueDaoClasses(element, daoMethods, entities)
+        validateUniqueIndices(element, entities)
+        val version = AnnotationMirrors.getAnnotationValue(dbAnnotation, "version")
+                .getAsInt(1)!!.toInt()
+        val exportSchema = AnnotationMirrors.getAnnotationValue(dbAnnotation, "exportSchema")
+                .getAsBoolean(true)
+
+        val hasForeignKeys = entities.any { it.foreignKeys.isNotEmpty() }
+
+        val database = Database(
+                version = version,
+                element = element,
+                type = MoreElements.asType(element).asType(),
+                entities = entities,
+                daoMethods = daoMethods,
+                exportSchema = exportSchema,
+                enableForeignKeys = hasForeignKeys)
+        return database
+    }
+
+    private fun validateForeignKeys(element: TypeElement, entities: List<Entity>) {
+        val byTableName = entities.associateBy { it.tableName }
+        entities.forEach { entity ->
+            entity.foreignKeys.forEach foreignKeyLoop@ { foreignKey ->
+                val parent = byTableName[foreignKey.parentTable]
+                if (parent == null) {
+                    context.logger.e(element, ProcessorErrors
+                            .foreignKeyMissingParentEntityInDatabase(foreignKey.parentTable,
+                                    entity.element.qualifiedName.toString()))
+                    return@foreignKeyLoop
+                }
+                val parentFields = foreignKey.parentColumns.map { columnName ->
+                    val parentField = parent.fields.find {
+                        it.columnName == columnName
+                    }
+                    if (parentField == null) {
+                        context.logger.e(entity.element,
+                                ProcessorErrors.foreignKeyParentColumnDoesNotExist(
+                                        parentEntity = parent.element.qualifiedName.toString(),
+                                        missingColumn = columnName,
+                                        allColumns = parent.fields.map { it.columnName }))
+                    }
+                    parentField
+                }.filterNotNull()
+                if (parentFields.size != foreignKey.parentColumns.size) {
+                    return@foreignKeyLoop
+                }
+                // ensure that it is indexed in the parent
+                if (!parent.isUnique(foreignKey.parentColumns)) {
+                    context.logger.e(parent.element, ProcessorErrors
+                            .foreignKeyMissingIndexInParent(
+                                    parentEntity = parent.element.qualifiedName.toString(),
+                                    childEntity = entity.element.qualifiedName.toString(),
+                                    parentColumns = foreignKey.parentColumns,
+                                    childColumns = foreignKey.childFields
+                                            .map { it.columnName }))
+                    return@foreignKeyLoop
+                }
+            }
+        }
+    }
+
+    private fun validateUniqueIndices(element: TypeElement, entities: List<Entity>) {
+        entities
+                .flatMap { entity ->
+                    // associate each index with its entity
+                    entity.indices.map { Pair(it.name, entity) }
+                }
+                .groupBy { it.first } // group by index name
+                .filter { it.value.size > 1 } // get the ones with duplicate names
+                .forEach {
+                    // do not report duplicates from the same entity
+                    if (it.value.distinctBy { it.second.typeName }.size > 1) {
+                        context.logger.e(element,
+                                ProcessorErrors.duplicateIndexInDatabase(it.key,
+                                        it.value.map { "${it.second.typeName} > ${it.first}" }))
+                    }
+                }
+    }
+
+    private fun validateUniqueDaoClasses(dbElement: TypeElement, daoMethods: List<DaoMethod>,
+                                         entities: List<Entity>) {
+        val entityTypeNames = entities.map { it.typeName }.toSet()
+        daoMethods.groupBy { it.dao.typeName }
+                .forEach {
+                    if (it.value.size > 1) {
+                        val error = ProcessorErrors.duplicateDao(it.key, it.value.map { it.name })
+                        it.value.forEach { daoMethod ->
+                            context.logger.e(daoMethod.element,
+                                    ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
+                        }
+                        // also report the full error for the database
+                        context.logger.e(dbElement, error)
+                    }
+                }
+        val check = fun(element: Element, dao: Dao,
+                        typeName: TypeName?) {
+            typeName?.let {
+                if (!entityTypeNames.contains(typeName)) {
+                    context.logger.e(element,
+                            ProcessorErrors.shortcutEntityIsNotInDatabase(
+                                    database = dbElement.qualifiedName.toString(),
+                                    dao = dao.typeName.toString(),
+                                    entity = typeName.toString()
+                            ))
+                }
+            }
+        }
+        daoMethods.forEach { daoMethod ->
+            daoMethod.dao.shortcutMethods.forEach { method ->
+                method.entities.forEach {
+                    check(method.element, daoMethod.dao, it.value.typeName)
+                }
+            }
+            daoMethod.dao.insertionMethods.forEach { method ->
+                method.entities.forEach {
+                    check(method.element, daoMethod.dao, it.value.typeName)
+                }
+            }
+        }
+    }
+
+    private fun validateUniqueTableNames(dbElement: TypeElement, entities: List<Entity>) {
+        entities
+                .groupBy {
+                    it.tableName.toLowerCase()
+                }.filter {
+            it.value.size > 1
+        }.forEach { byTableName ->
+            val error = ProcessorErrors.duplicateTableNames(byTableName.key,
+                    byTableName.value.map { it.typeName.toString() })
+            // report it for each of them and the database to make it easier
+            // for the developer
+            byTableName.value.forEach { entity ->
+                context.logger.e(entity.element, error)
+            }
+            context.logger.e(dbElement, error)
+        }
+    }
+
+    private fun processEntities(dbAnnotation: AnnotationMirror?, element: TypeElement):
+            List<Entity> {
+        if (!context.checker.check(dbAnnotation != null, element,
+                ProcessorErrors.DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE)) {
+            return listOf()
+        }
+
+        val entityList = AnnotationMirrors.getAnnotationValue(dbAnnotation, "entities")
+        val listOfTypes = entityList.toListOfClassTypes()
+        context.checker.check(listOfTypes.isNotEmpty(), element,
+                ProcessorErrors.DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES)
+        return listOfTypes.map {
+            EntityProcessor(context, MoreTypes.asTypeElement(it)).process()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/DeletionMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/DeletionMethodProcessor.kt
new file mode 100644
index 0000000..fc375ea
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/DeletionMethodProcessor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.processor
+
+import androidx.room.Delete
+import androidx.room.ext.typeName
+import androidx.room.vo.DeletionMethod
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+class DeletionMethodProcessor(baseContext: Context,
+                              val containing: DeclaredType,
+                              val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+
+    fun process(): DeletionMethod {
+        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
+        delegate.extractAnnotation(Delete::class, ProcessorErrors.MISSING_DELETE_ANNOTATION)
+
+        val returnTypeName = delegate.extractReturnType().typeName()
+        context.checker.check(
+                returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
+                executableElement,
+                ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+        )
+
+        val (entities, params) = delegate.extractParams(
+                missingParamError = ProcessorErrors
+                        .DELETION_MISSING_PARAMS
+        )
+
+        return DeletionMethod(
+                element = delegate.executableElement,
+                name = delegate.executableElement.simpleName.toString(),
+                entities = entities,
+                returnCount = returnTypeName == TypeName.INT,
+                parameters = params
+        )
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/EntityProcessor.kt
new file mode 100644
index 0000000..7d8b1f5
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/EntityProcessor.kt
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.ext.getAsBoolean
+import androidx.room.ext.getAsInt
+import androidx.room.ext.getAsString
+import androidx.room.ext.getAsStringList
+import androidx.room.ext.toType
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.parser.SqlParser
+import androidx.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
+import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import androidx.room.processor.cache.Cache
+import androidx.room.vo.EmbeddedField
+import androidx.room.vo.Entity
+import androidx.room.vo.Field
+import androidx.room.vo.ForeignKey
+import androidx.room.vo.ForeignKeyAction
+import androidx.room.vo.Index
+import androidx.room.vo.Pojo
+import androidx.room.vo.PrimaryKey
+import androidx.room.vo.Warning
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.AnnotationMirrors.getAnnotationValue
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.Name
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
+
+class EntityProcessor(baseContext: Context,
+                      val element: TypeElement,
+                      private val referenceStack: LinkedHashSet<Name> = LinkedHashSet()) {
+    val context = baseContext.fork(element)
+
+    fun process(): Entity {
+        return context.cache.entities.get(Cache.EntityKey(element), {
+            doProcess()
+        })
+    }
+    private fun doProcess(): Entity {
+        context.checker.hasAnnotation(element, androidx.room.Entity::class,
+                ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
+        val pojo = PojoProcessor(
+                baseContext = context,
+                element = element,
+                bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+                parent = null,
+                referenceStack = referenceStack).process()
+        context.checker.check(pojo.relations.isEmpty(), element, RELATION_IN_ENTITY)
+        val annotation = MoreElements.getAnnotationMirror(element,
+                androidx.room.Entity::class.java).orNull()
+        val tableName: String
+        val entityIndices: List<IndexInput>
+        val foreignKeyInputs: List<ForeignKeyInput>
+        val inheritSuperIndices: Boolean
+        if (annotation != null) {
+            tableName = extractTableName(element, annotation)
+            entityIndices = extractIndices(annotation, tableName)
+            inheritSuperIndices = AnnotationMirrors
+                    .getAnnotationValue(annotation, "inheritSuperIndices").getAsBoolean(false)
+            foreignKeyInputs = extractForeignKeys(annotation)
+        } else {
+            tableName = element.simpleName.toString()
+            foreignKeyInputs = emptyList()
+            entityIndices = emptyList()
+            inheritSuperIndices = false
+        }
+        context.checker.notBlank(tableName, element,
+                ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
+
+        val fieldIndices = pojo.fields
+                .filter { it.indexed }
+                .map {
+                    if (it.parent != null) {
+                        it.indexed = false
+                        context.logger.w(Warning.INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED, it.element,
+                                ProcessorErrors.droppedEmbeddedFieldIndex(
+                                        it.getPath(), element.qualifiedName.toString()))
+                        null
+                    } else if (it.element.enclosingElement != element && !inheritSuperIndices) {
+                        it.indexed = false
+                        context.logger.w(Warning.INDEX_FROM_PARENT_FIELD_IS_DROPPED,
+                                ProcessorErrors.droppedSuperClassFieldIndex(
+                                        it.columnName, element.toString(),
+                                        it.element.enclosingElement.toString()
+                                ))
+                        null
+                    } else {
+                        IndexInput(
+                                name = createIndexName(listOf(it.columnName), tableName),
+                                unique = false,
+                                columnNames = listOf(it.columnName)
+                        )
+                    }
+                }.filterNotNull()
+        val superIndices = loadSuperIndices(element.superclass, tableName, inheritSuperIndices)
+        val indexInputs = entityIndices + fieldIndices + superIndices
+        val indices = validateAndCreateIndices(indexInputs, pojo)
+
+        val primaryKey = findAndValidatePrimaryKey(pojo.fields, pojo.embeddedFields)
+        val affinity = primaryKey.fields.firstOrNull()?.affinity ?: SQLTypeAffinity.TEXT
+        context.checker.check(
+                !primaryKey.autoGenerateId || affinity == SQLTypeAffinity.INTEGER,
+                primaryKey.fields.firstOrNull()?.element ?: element,
+                ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT
+        )
+
+        val entityForeignKeys = validateAndCreateForeignKeyReferences(foreignKeyInputs, pojo)
+        checkIndicesForForeignKeys(entityForeignKeys, primaryKey, indices)
+
+        context.checker.check(SqlParser.isValidIdentifier(tableName), element,
+                ProcessorErrors.INVALID_TABLE_NAME)
+        pojo.fields.forEach {
+            context.checker.check(SqlParser.isValidIdentifier(it.columnName), it.element,
+                    ProcessorErrors.INVALID_COLUMN_NAME)
+        }
+
+        val entity = Entity(element = element,
+                tableName = tableName,
+                type = pojo.type,
+                fields = pojo.fields,
+                embeddedFields = pojo.embeddedFields,
+                indices = indices,
+                primaryKey = primaryKey,
+                foreignKeys = entityForeignKeys,
+                constructor = pojo.constructor)
+
+        return entity
+    }
+
+    private fun checkIndicesForForeignKeys(entityForeignKeys: List<ForeignKey>,
+                                           primaryKey: PrimaryKey,
+                                           indices: List<Index>) {
+        fun covers(columnNames: List<String>, fields: List<Field>): Boolean =
+            fields.size >= columnNames.size && columnNames.withIndex().all {
+                fields[it.index].columnName == it.value
+            }
+
+        entityForeignKeys.forEach { fKey ->
+            val columnNames = fKey.childFields.map { it.columnName }
+            val exists = covers(columnNames, primaryKey.fields) || indices.any { index ->
+                covers(columnNames, index.fields)
+            }
+            if (!exists) {
+                if (columnNames.size == 1) {
+                    context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element,
+                            ProcessorErrors.foreignKeyMissingIndexInChildColumn(columnNames[0]))
+                } else {
+                    context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element,
+                            ProcessorErrors.foreignKeyMissingIndexInChildColumns(columnNames))
+                }
+            }
+        }
+    }
+
+    /**
+     * Does a validation on foreign keys except the parent table's columns.
+     */
+    private fun validateAndCreateForeignKeyReferences(foreignKeyInputs: List<ForeignKeyInput>,
+                                                      pojo: Pojo): List<ForeignKey> {
+        return foreignKeyInputs.map {
+            if (it.onUpdate == null) {
+                context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
+                return@map null
+            }
+            if (it.onDelete == null) {
+                context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
+                return@map null
+            }
+            if (it.childColumns.isEmpty()) {
+                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST)
+                return@map null
+            }
+            if (it.parentColumns.isEmpty()) {
+                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST)
+                return@map null
+            }
+            if (it.childColumns.size != it.parentColumns.size) {
+                context.logger.e(element, ProcessorErrors.foreignKeyColumnNumberMismatch(
+                        it.childColumns, it.parentColumns
+                ))
+                return@map null
+            }
+            val parentElement = try {
+                MoreTypes.asElement(it.parent) as TypeElement
+            } catch (noClass: IllegalArgumentException) {
+                context.logger.e(element, ProcessorErrors.FOREIGN_KEY_CANNOT_FIND_PARENT)
+                return@map null
+            }
+            val parentAnnotation = MoreElements.getAnnotationMirror(parentElement,
+                    androidx.room.Entity::class.java).orNull()
+            if (parentAnnotation == null) {
+                context.logger.e(element,
+                        ProcessorErrors.foreignKeyNotAnEntity(parentElement.toString()))
+                return@map null
+            }
+            val tableName = extractTableName(parentElement, parentAnnotation)
+            val fields = it.childColumns.map { columnName ->
+                val field = pojo.fields.find { it.columnName == columnName }
+                if (field == null) {
+                    context.logger.e(pojo.element,
+                            ProcessorErrors.foreignKeyChildColumnDoesNotExist(columnName,
+                                    pojo.fields.map { it.columnName }))
+                }
+                field
+            }.filterNotNull()
+            if (fields.size != it.childColumns.size) {
+                return@map null
+            }
+            ForeignKey(
+                    parentTable = tableName,
+                    childFields = fields,
+                    parentColumns = it.parentColumns,
+                    onDelete = it.onDelete,
+                    onUpdate = it.onUpdate,
+                    deferred = it.deferred
+            )
+        }.filterNotNull()
+    }
+
+    private fun findAndValidatePrimaryKey(
+            fields: List<Field>, embeddedFields: List<EmbeddedField>): PrimaryKey {
+        val candidates = collectPrimaryKeysFromEntityAnnotations(element, fields) +
+                collectPrimaryKeysFromPrimaryKeyAnnotations(fields) +
+                collectPrimaryKeysFromEmbeddedFields(embeddedFields)
+
+        context.checker.check(candidates.isNotEmpty(), element, ProcessorErrors.MISSING_PRIMARY_KEY)
+
+        // 1. If a key is not autogenerated, but is Primary key or is part of Primary key we
+        // force the @NonNull annotation. If the key is a single Primary Key, Integer or Long, we
+        // don't force the @NonNull annotation since SQLite will automatically generate IDs.
+        // 2. If a key is autogenerate, we generate NOT NULL in table spec, but we don't require
+        // @NonNull annotation on the field itself.
+        candidates.filter { candidate -> !candidate.autoGenerateId }
+                .map { candidate ->
+                    candidate.fields.map { field ->
+                        if (candidate.fields.size > 1 ||
+                                (candidate.fields.size == 1
+                                        && field.affinity != SQLTypeAffinity.INTEGER)) {
+                            context.checker.check(field.nonNull, field.element,
+                                    ProcessorErrors.primaryKeyNull(field.getPath()))
+                            // Validate parents for nullability
+                            var parent = field.parent
+                            while (parent != null) {
+                                val parentField = parent.field
+                                context.checker.check(parentField.nonNull,
+                                        parentField.element,
+                                        ProcessorErrors.primaryKeyNull(parentField.getPath()))
+                                parent = parentField.parent
+                            }
+                        }
+                    }
+                }
+
+        if (candidates.size == 1) {
+            // easy :)
+            return candidates.first()
+        }
+
+        return choosePrimaryKey(candidates, element)
+    }
+
+    /**
+     * Check fields for @PrimaryKey.
+     */
+    private fun collectPrimaryKeysFromPrimaryKeyAnnotations(fields: List<Field>): List<PrimaryKey> {
+        return fields.map { field ->
+            MoreElements.getAnnotationMirror(field.element,
+                    androidx.room.PrimaryKey::class.java).orNull()?.let {
+                if (field.parent != null) {
+                    // the field in the entity that contains this error.
+                    val grandParentField = field.parent.mRootParent.field.element
+                    // bound for entity.
+                    context.fork(grandParentField).logger.w(
+                            Warning.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED,
+                            grandParentField,
+                            ProcessorErrors.embeddedPrimaryKeyIsDropped(
+                                    element.qualifiedName.toString(), field.name))
+                    null
+                } else {
+                    PrimaryKey(declaredIn = field.element.enclosingElement,
+                            fields = listOf(field),
+                            autoGenerateId = AnnotationMirrors
+                                    .getAnnotationValue(it, "autoGenerate")
+                                    .getAsBoolean(false))
+                }
+            }
+        }.filterNotNull()
+    }
+
+    /**
+     * Check classes for @Entity(primaryKeys = ?).
+     */
+    private fun collectPrimaryKeysFromEntityAnnotations(
+            typeElement: TypeElement, availableFields: List<Field>): List<PrimaryKey> {
+        val myPkeys = MoreElements.getAnnotationMirror(typeElement,
+                androidx.room.Entity::class.java).orNull()?.let {
+            val primaryKeyColumns = AnnotationMirrors.getAnnotationValue(it, "primaryKeys")
+                    .getAsStringList()
+            if (primaryKeyColumns.isEmpty()) {
+                emptyList<PrimaryKey>()
+            } else {
+                val fields = primaryKeyColumns.map { pKeyColumnName ->
+                    val field = availableFields.firstOrNull { it.columnName == pKeyColumnName }
+                    context.checker.check(field != null, typeElement,
+                            ProcessorErrors.primaryKeyColumnDoesNotExist(pKeyColumnName,
+                                    availableFields.map { it.columnName }))
+                    field
+                }.filterNotNull()
+                listOf(PrimaryKey(declaredIn = typeElement,
+                        fields = fields,
+                        autoGenerateId = false))
+            }
+        } ?: emptyList<PrimaryKey>()
+        // checks supers.
+        val mySuper = typeElement.superclass
+        val superPKeys = if (mySuper != null && mySuper.kind != TypeKind.NONE) {
+            // my super cannot see my fields so remove them.
+            val remainingFields = availableFields.filterNot {
+                it.element.enclosingElement == typeElement
+            }
+            collectPrimaryKeysFromEntityAnnotations(
+                    MoreTypes.asTypeElement(mySuper), remainingFields)
+        } else {
+            emptyList()
+        }
+        return superPKeys + myPkeys
+    }
+
+    private fun collectPrimaryKeysFromEmbeddedFields(
+            embeddedFields: List<EmbeddedField>): List<PrimaryKey> {
+        return embeddedFields.map { embeddedField ->
+            MoreElements.getAnnotationMirror(embeddedField.field.element,
+                    androidx.room.PrimaryKey::class.java).orNull()?.let {
+                val autoGenerate = AnnotationMirrors
+                        .getAnnotationValue(it, "autoGenerate").getAsBoolean(false)
+                context.checker.check(!autoGenerate || embeddedField.pojo.fields.size == 1,
+                        embeddedField.field.element,
+                        ProcessorErrors.AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS)
+                PrimaryKey(declaredIn = embeddedField.field.element.enclosingElement,
+                        fields = embeddedField.pojo.fields,
+                        autoGenerateId = autoGenerate)
+            }
+        }.filterNotNull()
+    }
+
+    // start from my element and check if anywhere in the list we can find the only well defined
+    // pkey, if so, use it.
+    private fun choosePrimaryKey(
+            candidates: List<PrimaryKey>, typeElement: TypeElement): PrimaryKey {
+        // If 1 of these primary keys is declared in this class, then it is the winner. Just print
+        //    a note for the others.
+        // If 0 is declared, check the parent.
+        // If more than 1 primary key is declared in this class, it is an error.
+        val myPKeys = candidates.filter { candidate ->
+            candidate.declaredIn == typeElement
+        }
+        return if (myPKeys.size == 1) {
+            // just note, this is not worth an error or warning
+            (candidates - myPKeys).forEach {
+                context.logger.d(element,
+                        "${it.toHumanReadableString()} is" +
+                                " overridden by ${myPKeys.first().toHumanReadableString()}")
+            }
+            myPKeys.first()
+        } else if (myPKeys.isEmpty()) {
+            // i have not declared anything, delegate to super
+            val mySuper = typeElement.superclass
+            if (mySuper != null && mySuper.kind != TypeKind.NONE) {
+                return choosePrimaryKey(candidates, MoreTypes.asTypeElement(mySuper))
+            }
+            PrimaryKey.MISSING
+        } else {
+            context.logger.e(element, ProcessorErrors.multiplePrimaryKeyAnnotations(
+                    myPKeys.map(PrimaryKey::toHumanReadableString)))
+            PrimaryKey.MISSING
+        }
+    }
+
+    private fun validateAndCreateIndices(
+            inputs: List<IndexInput>, pojo: Pojo): List<Index> {
+        // check for columns
+        val indices = inputs.map { input ->
+            context.checker.check(input.columnNames.isNotEmpty(), element,
+                    INDEX_COLUMNS_CANNOT_BE_EMPTY)
+            val fields = input.columnNames.map { columnName ->
+                val field = pojo.fields.firstOrNull {
+                    it.columnName == columnName
+                }
+                context.checker.check(field != null, element,
+                        ProcessorErrors.indexColumnDoesNotExist(
+                                columnName, pojo.fields.map { it.columnName }
+                        ))
+                field
+            }.filterNotNull()
+            if (fields.isEmpty()) {
+                null
+            } else {
+                Index(name = input.name, unique = input.unique, fields = fields)
+            }
+        }.filterNotNull()
+
+        // check for duplicate indices
+        indices
+                .groupBy { it.name }
+                .filter { it.value.size > 1 }
+                .forEach {
+                    context.logger.e(element, ProcessorErrors.duplicateIndexInEntity(it.key))
+                }
+
+        // see if any embedded field is an entity with indices, if so, report a warning
+        pojo.embeddedFields.forEach { embedded ->
+            val embeddedElement = embedded.pojo.element
+            val subEntityAnnotation = MoreElements.getAnnotationMirror(embeddedElement,
+                    androidx.room.Entity::class.java).orNull()
+            subEntityAnnotation?.let {
+                val subIndices = extractIndices(subEntityAnnotation, "")
+                if (subIndices.isNotEmpty()) {
+                    context.logger.w(Warning.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED,
+                            embedded.field.element, ProcessorErrors.droppedEmbeddedIndex(
+                            entityName = embedded.pojo.typeName.toString(),
+                            fieldPath = embedded.field.getPath(),
+                            grandParent = element.qualifiedName.toString()))
+                }
+            }
+        }
+        return indices
+    }
+
+    // check if parent is an Entity, if so, report its annotation indices
+    private fun loadSuperIndices(
+            typeMirror: TypeMirror?, tableName: String, inherit: Boolean): List<IndexInput> {
+        if (typeMirror == null || typeMirror.kind == TypeKind.NONE) {
+            return emptyList()
+        }
+        val parentElement = MoreTypes.asTypeElement(typeMirror)
+        val myIndices = MoreElements.getAnnotationMirror(parentElement,
+                androidx.room.Entity::class.java).orNull()?.let { annotation ->
+            val indices = extractIndices(annotation, tableName = "super")
+            if (indices.isEmpty()) {
+                emptyList()
+            } else if (inherit) {
+                // rename them
+                indices.map {
+                    IndexInput(
+                            name = createIndexName(it.columnNames, tableName),
+                            unique = it.unique,
+                            columnNames = it.columnNames)
+                }
+            } else {
+                context.logger.w(Warning.INDEX_FROM_PARENT_IS_DROPPED,
+                        parentElement,
+                        ProcessorErrors.droppedSuperClassIndex(
+                                childEntity = element.qualifiedName.toString(),
+                                superEntity = parentElement.qualifiedName.toString()))
+                emptyList()
+            }
+        } ?: emptyList()
+        return myIndices + loadSuperIndices(parentElement.superclass, tableName, inherit)
+    }
+
+    companion object {
+        fun extractTableName(element: TypeElement, annotation: AnnotationMirror): String {
+            val annotationValue = AnnotationMirrors
+                    .getAnnotationValue(annotation, "tableName").value.toString()
+            return if (annotationValue == "") {
+                element.simpleName.toString()
+            } else {
+                annotationValue
+            }
+        }
+
+        private fun extractIndices(
+                annotation: AnnotationMirror, tableName: String): List<IndexInput> {
+            val arrayOfIndexAnnotations = AnnotationMirrors.getAnnotationValue(annotation,
+                    "indices")
+            return INDEX_LIST_VISITOR.visit(arrayOfIndexAnnotations, tableName)
+        }
+
+        private val INDEX_LIST_VISITOR = object
+            : SimpleAnnotationValueVisitor6<List<IndexInput>, String>() {
+            override fun visitArray(
+                    values: MutableList<out AnnotationValue>?,
+                    tableName: String
+            ): List<IndexInput> {
+                return values?.map {
+                    INDEX_VISITOR.visit(it, tableName)
+                }?.filterNotNull() ?: emptyList<IndexInput>()
+            }
+        }
+
+        private val INDEX_VISITOR = object : SimpleAnnotationValueVisitor6<IndexInput?, String>() {
+            override fun visitAnnotation(a: AnnotationMirror?, tableName: String): IndexInput? {
+                val fieldInput = getAnnotationValue(a, "value").getAsStringList()
+                val unique = getAnnotationValue(a, "unique").getAsBoolean(false)
+                val nameValue = getAnnotationValue(a, "name")
+                        .getAsString("")
+                val name = if (nameValue == null || nameValue == "") {
+                    createIndexName(fieldInput, tableName)
+                } else {
+                    nameValue
+                }
+                return IndexInput(name, unique, fieldInput)
+            }
+        }
+
+        private fun createIndexName(columnNames: List<String>, tableName: String): String {
+            return Index.DEFAULT_PREFIX + tableName + "_" + columnNames.joinToString("_")
+        }
+
+        private fun extractForeignKeys(annotation: AnnotationMirror): List<ForeignKeyInput> {
+            val arrayOfForeignKeyAnnotations = getAnnotationValue(annotation, "foreignKeys")
+            return FOREIGN_KEY_LIST_VISITOR.visit(arrayOfForeignKeyAnnotations)
+        }
+
+        private val FOREIGN_KEY_LIST_VISITOR = object
+            : SimpleAnnotationValueVisitor6<List<ForeignKeyInput>, Void?>() {
+            override fun visitArray(
+                    values: MutableList<out AnnotationValue>?,
+                    void: Void?
+            ): List<ForeignKeyInput> {
+                return values?.map {
+                    FOREIGN_KEY_VISITOR.visit(it)
+                }?.filterNotNull() ?: emptyList<ForeignKeyInput>()
+            }
+        }
+
+        private val FOREIGN_KEY_VISITOR = object : SimpleAnnotationValueVisitor6<ForeignKeyInput?,
+                Void?>() {
+            override fun visitAnnotation(a: AnnotationMirror?, void: Void?): ForeignKeyInput? {
+                val entityClass = try {
+                    getAnnotationValue(a, "entity").toType()
+                } catch (notPresent: TypeNotPresentException) {
+                    return null
+                }
+                val parentColumns = getAnnotationValue(a, "parentColumns").getAsStringList()
+                val childColumns = getAnnotationValue(a, "childColumns").getAsStringList()
+                val onDeleteInput = getAnnotationValue(a, "onDelete").getAsInt()
+                val onUpdateInput = getAnnotationValue(a, "onUpdate").getAsInt()
+                val deferred = getAnnotationValue(a, "deferred").getAsBoolean(true)
+                val onDelete = ForeignKeyAction.fromAnnotationValue(onDeleteInput)
+                val onUpdate = ForeignKeyAction.fromAnnotationValue(onUpdateInput)
+                return ForeignKeyInput(
+                        parent = entityClass,
+                        parentColumns = parentColumns,
+                        childColumns = childColumns,
+                        onDelete = onDelete,
+                        onUpdate = onUpdate,
+                        deferred = deferred)
+            }
+        }
+    }
+
+    /**
+     * processed Index annotation output
+     */
+    data class IndexInput(val name: String, val unique: Boolean, val columnNames: List<String>)
+
+    /**
+     * ForeignKey, before it is processed in the context of a database.
+     */
+    data class ForeignKeyInput(
+            val parent: TypeMirror,
+            val parentColumns: List<String>,
+            val childColumns: List<String>,
+            val onDelete: ForeignKeyAction?,
+            val onUpdate: ForeignKeyAction?,
+            val deferred: Boolean)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
new file mode 100644
index 0000000..496565b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.ColumnInfo
+import androidx.room.ext.getAsBoolean
+import androidx.room.ext.getAsInt
+import androidx.room.ext.getAsString
+import androidx.room.parser.Collate
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.vo.EmbeddedField
+import androidx.room.vo.Field
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.Element
+import javax.lang.model.type.DeclaredType
+
+class FieldProcessor(baseContext: Context, val containing: DeclaredType, val element: Element,
+                     val bindingScope: BindingScope,
+                     // pass only if this is processed as a child of Embedded field
+                     val fieldParent: EmbeddedField?) {
+    val context = baseContext.fork(element)
+    fun process(): Field {
+        val member = context.processingEnv.typeUtils.asMemberOf(containing, element)
+        val type = TypeName.get(member)
+        val columnInfoAnnotation = MoreElements.getAnnotationMirror(element,
+                ColumnInfo::class.java)
+        val name = element.simpleName.toString()
+        val columnName: String
+        val affinity: SQLTypeAffinity?
+        val collate: Collate?
+        val fieldPrefix = fieldParent?.prefix ?: ""
+        val indexed: Boolean
+        if (columnInfoAnnotation.isPresent) {
+            val nameInAnnotation = AnnotationMirrors
+                    .getAnnotationValue(columnInfoAnnotation.get(), "name")
+                    .getAsString(ColumnInfo.INHERIT_FIELD_NAME)
+            columnName = fieldPrefix + if (nameInAnnotation == ColumnInfo.INHERIT_FIELD_NAME) {
+                name
+            } else {
+                nameInAnnotation
+            }
+
+            affinity = try {
+                val userDefinedAffinity = AnnotationMirrors
+                        .getAnnotationValue(columnInfoAnnotation.get(), "typeAffinity")
+                        .getAsInt(ColumnInfo.UNDEFINED)!!
+                SQLTypeAffinity.fromAnnotationValue(userDefinedAffinity)
+            } catch (ex: NumberFormatException) {
+                null
+            }
+
+            collate = Collate.fromAnnotationValue(AnnotationMirrors.getAnnotationValue(
+                    columnInfoAnnotation.get(), "collate").getAsInt(ColumnInfo.UNSPECIFIED)!!)
+
+            indexed = AnnotationMirrors
+                    .getAnnotationValue(columnInfoAnnotation.get(), "index")
+                    .getAsBoolean(false)
+        } else {
+            columnName = fieldPrefix + name
+            affinity = null
+            collate = null
+            indexed = false
+        }
+        context.checker.notBlank(columnName, element,
+                ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
+        context.checker.notUnbound(type, element,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
+
+        val field = Field(name = name,
+                type = member,
+                element = element,
+                columnName = columnName,
+                affinity = affinity,
+                collate = collate,
+                parent = fieldParent,
+                indexed = indexed)
+
+        when (bindingScope) {
+            BindingScope.TWO_WAY -> {
+                val adapter = context.typeAdapterStore.findColumnTypeAdapter(field.type,
+                        field.affinity)
+                field.statementBinder = adapter
+                field.cursorValueReader = adapter
+                field.affinity = adapter?.typeAffinity ?: field.affinity
+                context.checker.check(adapter != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+            }
+            BindingScope.BIND_TO_STMT -> {
+                field.statementBinder = context.typeAdapterStore
+                        .findStatementValueBinder(field.type, field.affinity)
+                context.checker.check(field.statementBinder != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_STMT_BINDER)
+            }
+            BindingScope.READ_FROM_CURSOR -> {
+                field.cursorValueReader = context.typeAdapterStore
+                        .findCursorValueReader(field.type, field.affinity)
+                context.checker.check(field.cursorValueReader != null, field.element,
+                        ProcessorErrors.CANNOT_FIND_CURSOR_READER)
+            }
+        }
+        return field
+    }
+
+    /**
+     * Defines what we need to assign
+     */
+    enum class BindingScope {
+        TWO_WAY, // both bind and read.
+        BIND_TO_STMT, // just value to statement
+        READ_FROM_CURSOR // just cursor to value
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt
new file mode 100644
index 0000000..01ec6a5
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+
+package androidx.room.processor
+
+import androidx.annotation.VisibleForTesting
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy.IGNORE
+import androidx.room.OnConflictStrategy.REPLACE
+import androidx.room.vo.InsertionMethod
+import androidx.room.vo.InsertionMethod.Type
+import androidx.room.vo.ShortcutQueryParameter
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.VOID
+import javax.lang.model.type.TypeMirror
+
+class InsertionMethodProcessor(baseContext: Context,
+                               val containing: DeclaredType,
+                               val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+    fun process(): InsertionMethod {
+        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
+        val annotation = delegate.extractAnnotation(Insert::class,
+                ProcessorErrors.MISSING_INSERT_ANNOTATION)
+
+        val onConflict = OnConflictProcessor.extractFrom(annotation)
+        context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE,
+                executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+
+        val returnType = delegate.extractReturnType()
+        val returnTypeName = TypeName.get(returnType)
+        context.checker.notUnbound(returnTypeName, executableElement,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS)
+
+        val (entities, params) = delegate.extractParams(
+                missingParamError = ProcessorErrors
+                        .INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
+        )
+
+        // TODO we can support more types
+        var insertionType = getInsertionType(returnType)
+        context.checker.check(insertionType != null, executableElement,
+                ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE)
+
+        if (insertionType != null) {
+            val acceptable = acceptableTypes(params)
+            if (insertionType !in acceptable) {
+                context.logger.e(executableElement,
+                        ProcessorErrors.insertionMethodReturnTypeMismatch(
+                                insertionType.returnTypeName,
+                                acceptable.map { it.returnTypeName }))
+                // clear it, no reason to generate code for it.
+                insertionType = null
+            }
+        }
+        return InsertionMethod(
+                element = executableElement,
+                name = executableElement.simpleName.toString(),
+                returnType = returnType,
+                entities = entities,
+                parameters = params,
+                onConflict = onConflict,
+                insertionType = insertionType
+        )
+    }
+
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    private fun getInsertionType(returnType: TypeMirror): InsertionMethod.Type? {
+        // TODO we need to support more types here.
+        fun isLongPrimitiveType(typeMirror: TypeMirror) = typeMirror.kind == LONG
+
+        fun isLongBoxType(typeMirror: TypeMirror) =
+                MoreTypes.isType(typeMirror) &&
+                        MoreTypes.isTypeOf(java.lang.Long::class.java, typeMirror)
+
+        fun isLongType(typeMirror: TypeMirror) =
+                isLongPrimitiveType(typeMirror) || isLongBoxType(typeMirror)
+
+        return if (returnType.kind == VOID) {
+            Type.INSERT_VOID
+        } else if (returnType.kind == TypeKind.ARRAY) {
+            val arrayType = MoreTypes.asArray(returnType)
+            val param = arrayType.componentType
+            if (isLongPrimitiveType(param)) {
+                Type.INSERT_ID_ARRAY
+            } else if (isLongBoxType(param)) {
+                Type.INSERT_ID_ARRAY_BOX
+            } else {
+                null
+            }
+        } else if (MoreTypes.isType(returnType)
+                && MoreTypes.isTypeOf(List::class.java, returnType)) {
+            val declared = MoreTypes.asDeclared(returnType)
+            val param = declared.typeArguments.first()
+            if (isLongBoxType(param)) {
+                Type.INSERT_ID_LIST
+            } else {
+                null
+            }
+        } else if (isLongType(returnType)) {
+            Type.INSERT_SINGLE_ID
+        } else {
+            null
+        }
+    }
+
+    companion object {
+        @VisibleForTesting
+        val VOID_SET by lazy { setOf(Type.INSERT_VOID) }
+        @VisibleForTesting
+        val SINGLE_ITEM_SET by lazy { setOf(Type.INSERT_VOID, Type.INSERT_SINGLE_ID) }
+        @VisibleForTesting
+        val MULTIPLE_ITEM_SET by lazy {
+            setOf(Type.INSERT_VOID, Type.INSERT_ID_ARRAY, Type.INSERT_ID_ARRAY_BOX,
+                    Type.INSERT_ID_LIST)
+        }
+        fun acceptableTypes(params: List<ShortcutQueryParameter>): Set<InsertionMethod.Type> {
+            if (params.isEmpty()) {
+                return VOID_SET
+            }
+            if (params.size > 1) {
+                return VOID_SET
+            }
+            if (params.first().isMultiple) {
+                return MULTIPLE_ITEM_SET
+            } else {
+                return SINGLE_ITEM_SET
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/OnConflictProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/OnConflictProcessor.kt
new file mode 100644
index 0000000..537f685
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/OnConflictProcessor.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.OnConflictStrategy
+import com.google.auto.common.AnnotationMirrors
+import javax.lang.model.element.AnnotationMirror
+
+/**
+ * Processes on conflict fields in annotations
+ */
+object OnConflictProcessor {
+    val INVALID_ON_CONFLICT = -1
+
+    @OnConflictStrategy
+    fun extractFrom(annotation: AnnotationMirror?, fieldName: String = "onConflict"): Int {
+        return if (annotation == null) {
+            INVALID_ON_CONFLICT
+        } else {
+            try {
+                val onConflictValue = AnnotationMirrors
+                        .getAnnotationValue(annotation, fieldName)
+                        .value
+                onConflictValue.toString().toInt()
+            } catch (ex: NumberFormatException) {
+                INVALID_ON_CONFLICT
+            }
+        }
+    }
+
+    fun onConflictText(@OnConflictStrategy onConflict: Int): String {
+        return when (onConflict) {
+            OnConflictStrategy.REPLACE -> "REPLACE"
+            OnConflictStrategy.ABORT -> "ABORT"
+            OnConflictStrategy.FAIL -> "FAIL"
+            OnConflictStrategy.IGNORE -> "IGNORE"
+            OnConflictStrategy.ROLLBACK -> "ROLLBACK"
+            else -> "BAD_CONFLICT_CONSTRAINT"
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/PojoMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/PojoMethodProcessor.kt
new file mode 100644
index 0000000..fc2f33a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/PojoMethodProcessor.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 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 androidx.room.processor
+
+import androidx.room.vo.PojoMethod
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+/**
+ * processes an executable element as member of the owning class
+ */
+class PojoMethodProcessor(
+        private val context: Context,
+        private val element: ExecutableElement,
+        private val owner: DeclaredType) {
+    fun process(): PojoMethod {
+        val asMember = context.processingEnv.typeUtils.asMemberOf(owner, element)
+        val name = element.simpleName.toString()
+        return PojoMethod(
+                element = element,
+                resolvedType = MoreTypes.asExecutable(asMember),
+                name = name
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
new file mode 100644
index 0000000..e04cbb5
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -0,0 +1,726 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.ColumnInfo
+import androidx.room.Embedded
+import androidx.room.Ignore
+import androidx.room.Relation
+import androidx.room.ext.KotlinMetadataProcessor
+import androidx.room.ext.getAllFieldsIncludingPrivateSupers
+import androidx.room.ext.getAnnotationValue
+import androidx.room.ext.getAsString
+import androidx.room.ext.getAsStringList
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.hasAnyOf
+import androidx.room.ext.isAssignableWithoutVariance
+import androidx.room.ext.isCollection
+import androidx.room.ext.toClassType
+import androidx.room.ext.typeName
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
+import androidx.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+import androidx.room.processor.cache.Cache
+import androidx.room.vo.CallType
+import androidx.room.vo.Constructor
+import androidx.room.vo.EmbeddedField
+import androidx.room.vo.Entity
+import androidx.room.vo.Field
+import androidx.room.vo.FieldGetter
+import androidx.room.vo.FieldSetter
+import androidx.room.vo.Pojo
+import androidx.room.vo.PojoMethod
+import androidx.room.vo.Warning
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
+import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier.ABSTRACT
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PROTECTED
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.element.Modifier.STATIC
+import javax.lang.model.element.Modifier.TRANSIENT
+import javax.lang.model.element.Name
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
+
+/**
+ * Processes any class as if it is a Pojo.
+ */
+class PojoProcessor(
+        baseContext: Context,
+        val element: TypeElement,
+        val bindingScope: FieldProcessor.BindingScope,
+        val parent: EmbeddedField?,
+        val referenceStack: LinkedHashSet<Name> = LinkedHashSet())
+    : KotlinMetadataProcessor {
+    val context = baseContext.fork(element)
+
+    // for KotlinMetadataUtils
+    override val processingEnv: ProcessingEnvironment
+        get() = context.processingEnv
+
+    // opportunistic kotlin metadata
+    private val kotlinMetadata by lazy {
+        try {
+            element.kotlinMetadata
+        } catch (throwable: Throwable) {
+            context.logger.d(element, "failed to read get kotlin metadata from %s", element)
+        } as? KotlinClassMetadata
+    }
+
+    companion object {
+        val PROCESSED_ANNOTATIONS = listOf(ColumnInfo::class, Embedded::class,
+                Relation::class)
+    }
+
+    fun process(): Pojo {
+        return context.cache.pojos.get(Cache.PojoKey(element, bindingScope, parent), {
+            referenceStack.add(element.qualifiedName)
+            try {
+                doProcess()
+            } finally {
+                referenceStack.remove(element.qualifiedName)
+            }
+        })
+    }
+
+    private fun doProcess(): Pojo {
+        val declaredType = MoreTypes.asDeclared(element.asType())
+        // TODO handle conflicts with super: b/35568142
+        val allFields = element.getAllFieldsIncludingPrivateSupers(context.processingEnv)
+                .filter {
+                    !it.hasAnnotation(Ignore::class)
+                            && !it.hasAnyOf(STATIC)
+                            && (!it.hasAnyOf(TRANSIENT)
+                            || it.hasAnnotation(ColumnInfo::class)
+                            || it.hasAnnotation(Embedded::class)
+                            || it.hasAnnotation(Relation::class))
+                }
+                .groupBy { field ->
+                    context.checker.check(
+                            PROCESSED_ANNOTATIONS.count { field.hasAnnotation(it) } < 2, field,
+                            ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION
+                    )
+                    if (field.hasAnnotation(Embedded::class)) {
+                        Embedded::class
+                    } else if (field.hasAnnotation(Relation::class)) {
+                        Relation::class
+                    } else {
+                        null
+                    }
+                }
+
+        val myFields = allFields[null]
+                ?.map {
+                    FieldProcessor(
+                            baseContext = context,
+                            containing = declaredType,
+                            element = it,
+                            bindingScope = bindingScope,
+                            fieldParent = parent).process()
+                } ?: emptyList()
+
+        val embeddedFields =
+                allFields[Embedded::class]
+                        ?.map {
+                            processEmbeddedField(declaredType, it)
+                        }
+                        ?.filterNotNull()
+                        ?: emptyList()
+
+        val subFields = embeddedFields.flatMap { it.pojo.fields }
+        val fields = myFields + subFields
+
+        val myRelationsList = allFields[Relation::class]
+                ?.map {
+                    processRelationField(fields, declaredType, it)
+                }
+                ?.filterNotNull()
+                ?: emptyList()
+
+        val subRelations = embeddedFields.flatMap { it.pojo.relations }
+        val relations = myRelationsList + subRelations
+
+        fields.groupBy { it.columnName }
+                .filter { it.value.size > 1 }
+                .forEach {
+                    context.logger.e(element, ProcessorErrors.pojoDuplicateFieldNames(
+                            it.key, it.value.map(Field::getPath)
+                    ))
+                    it.value.forEach {
+                        context.logger.e(it.element, POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME)
+                    }
+                }
+
+        val methods = MoreElements.getLocalAndInheritedMethods(element,
+                context.processingEnv.elementUtils)
+                .filter {
+                    !it.hasAnyOf(PRIVATE, ABSTRACT, STATIC)
+                            && !it.hasAnnotation(Ignore::class)
+                }
+                .map { MoreElements.asExecutable(it) }
+                .map {
+                    PojoMethodProcessor(
+                            context = context,
+                            element = it,
+                            owner = declaredType
+                    ).process()
+                }
+
+        val getterCandidates = methods.filter {
+            it.element.parameters.size == 0 && it.resolvedType.returnType.kind != TypeKind.VOID
+        }
+
+        val setterCandidates = methods.filter {
+            it.element.parameters.size == 1 && it.resolvedType.returnType.kind == TypeKind.VOID
+        }
+
+        // don't try to find a constructor for binding to statement.
+        val constructor = if (bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT) {
+            // we don't need to construct this POJO.
+            null
+        } else {
+            chooseConstructor(myFields, embeddedFields, relations)
+        }
+
+        assignGetters(myFields, getterCandidates)
+        assignSetters(myFields, setterCandidates, constructor)
+
+        embeddedFields.forEach {
+            assignGetter(it.field, getterCandidates)
+            assignSetter(it.field, setterCandidates, constructor)
+        }
+
+        myRelationsList.forEach {
+            assignGetter(it.field, getterCandidates)
+            assignSetter(it.field, setterCandidates, constructor)
+        }
+
+        return Pojo(element = element,
+                type = declaredType,
+                fields = fields,
+                embeddedFields = embeddedFields,
+                relations = relations,
+                constructor = constructor)
+    }
+
+    /**
+     * Retrieves the parameter names of a method. If the method is inherited from a dependency
+     * module, the parameter name is not available (not in java spec). For kotlin, since parameter
+     * names are part of the API, we can read them via the kotlin metadata annotation.
+     * <p>
+     * Since we are using an unofficial library to read the metadata, all access to that code
+     * is safe guarded to avoid unexpected failures. In other words, it is a best effort but
+     * better than not supporting these until JB provides a proper API.
+     */
+    private fun getParamNames(method: ExecutableElement): List<String> {
+        val paramNames = method.parameters.map { it.simpleName.toString() }
+        if (paramNames.isEmpty()) {
+            return emptyList()
+        }
+        return kotlinMetadata?.getParameterNames(method) ?: paramNames
+    }
+
+    private fun chooseConstructor(
+            myFields: List<Field>,
+            embedded: List<EmbeddedField>,
+            relations: List<androidx.room.vo.Relation>): Constructor? {
+        val constructors = ElementFilter.constructorsIn(element.enclosedElements)
+                .filterNot { it.hasAnnotation(Ignore::class) || it.hasAnyOf(PRIVATE) }
+        val fieldMap = myFields.associateBy { it.name }
+        val embeddedMap = embedded.associateBy { it.field.name }
+        val typeUtils = context.processingEnv.typeUtils
+        // list of param names -> matched params pairs for each failed constructor
+        val failedConstructors = arrayListOf<FailedConstructor>()
+        // if developer puts a relation into a constructor, it is usually an error but if there
+        // is another constructor that is good, we can ignore the error. b/72884434
+        val relationsInConstructor = arrayListOf<VariableElement>()
+        val goodConstructors = constructors.map { constructor ->
+            val parameterNames = getParamNames(constructor)
+            val params = constructor.parameters.mapIndexed param@ { index, param ->
+                val paramName = parameterNames[index]
+                val paramType = param.asType()
+
+                val matches = fun(field: Field?): Boolean {
+                    return if (field == null) {
+                        false
+                    } else if (!field.nameWithVariations.contains(paramName)) {
+                        false
+                    } else {
+                        // see: b/69164099
+                        typeUtils.isAssignableWithoutVariance(paramType, field.type)
+                    }
+                }
+
+                val exactFieldMatch = fieldMap[paramName]
+
+                if (matches(exactFieldMatch)) {
+                    return@param Constructor.FieldParam(exactFieldMatch!!)
+                }
+                val exactEmbeddedMatch = embeddedMap[paramName]
+                if (matches(exactEmbeddedMatch?.field)) {
+                    return@param Constructor.EmbeddedParam(exactEmbeddedMatch!!)
+                }
+
+                val matchingFields = myFields.filter {
+                    matches(it)
+                }
+                val embeddedMatches = embedded.filter {
+                    matches(it.field)
+                }
+                if (matchingFields.isEmpty() && embeddedMatches.isEmpty()) {
+                    // if it didn't match a proper field, a common mistake is to have a relation
+                    // so check to see if it is a relation
+                    val matchedRelation = relations.any {
+                        it.field.nameWithVariations.contains(paramName)
+                    }
+                    if (matchedRelation) {
+                        relationsInConstructor.add(param)
+                    }
+                    null
+                } else if (matchingFields.size + embeddedMatches.size == 1) {
+                    if (matchingFields.isNotEmpty()) {
+                        Constructor.FieldParam(matchingFields.first())
+                    } else {
+                        Constructor.EmbeddedParam(embeddedMatches.first())
+                    }
+                } else {
+                    context.logger.e(param, ProcessorErrors.ambigiousConstructor(
+                            pojo = element.qualifiedName.toString(),
+                            paramName = paramName,
+                            matchingFields = matchingFields.map { it.getPath() }
+                                    + embedded.map { it.field.getPath() }
+                    ))
+                    null
+                }
+            }
+            if (params.any { it == null }) {
+                failedConstructors.add(FailedConstructor(constructor, parameterNames, params))
+                null
+            } else {
+                @Suppress("UNCHECKED_CAST")
+                Constructor(constructor, params as List<Constructor.Param>)
+            }
+        }.filterNotNull()
+        when {
+            goodConstructors.isEmpty() -> {
+                relationsInConstructor.forEach {
+                    context.logger.e(it,
+                            ProcessorErrors.RELATION_CANNOT_BE_CONSTRUCTOR_PARAMETER)
+                }
+                if (failedConstructors.isNotEmpty()) {
+                    val failureMsg = failedConstructors.joinToString("\n") { entry ->
+                        entry.log()
+                    }
+                    context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR +
+                            "\nTried the following constructors but they failed to match:" +
+                            "\n$failureMsg")
+                }
+                context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+                return null
+            }
+            goodConstructors.size > 1 -> {
+                // if there is a no-arg constructor, pick it. Even though it is weird, easily happens
+                // with kotlin data classes.
+                val noArg = goodConstructors.firstOrNull { it.params.isEmpty() }
+                if (noArg != null) {
+                    context.logger.w(Warning.DEFAULT_CONSTRUCTOR, element,
+                            ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS_CHOOSING_NO_ARG)
+                    return noArg
+                }
+                goodConstructors.forEach {
+                    context.logger.e(it.element, ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
+                }
+                return null
+            }
+            else -> return goodConstructors.first()
+        }
+    }
+
+    private fun processEmbeddedField(
+            declaredType: DeclaredType?, variableElement: VariableElement): EmbeddedField? {
+
+        val asTypeElement = MoreTypes.asTypeElement(variableElement.asType())
+
+        if (detectReferenceRecursion(asTypeElement)) {
+            return null
+        }
+
+        val fieldPrefix = variableElement
+                .getAnnotationValue(Embedded::class.java, "prefix")
+                ?.toString()
+                ?: ""
+        val inheritedPrefix = parent?.prefix ?: ""
+        val embeddedField = Field(
+                variableElement,
+                variableElement.simpleName.toString(),
+                type = context
+                        .processingEnv
+                        .typeUtils
+                        .asMemberOf(declaredType, variableElement),
+                affinity = null,
+                parent = parent)
+        val subParent = EmbeddedField(
+                field = embeddedField,
+                prefix = inheritedPrefix + fieldPrefix,
+                parent = parent)
+        subParent.pojo = PojoProcessor(
+                baseContext = context.fork(variableElement),
+                element = asTypeElement,
+                bindingScope = bindingScope,
+                parent = subParent,
+                referenceStack = referenceStack).process()
+        return subParent
+    }
+
+    private fun processRelationField(
+            myFields: List<Field>, container: DeclaredType?,
+            relationElement: VariableElement
+    ): androidx.room.vo.Relation? {
+        val asTypeElement = MoreTypes.asTypeElement(
+                MoreElements.asVariable(relationElement).asType())
+
+        if (detectReferenceRecursion(asTypeElement)) {
+            return null
+        }
+
+        val annotation = MoreElements.getAnnotationMirror(relationElement, Relation::class.java)
+                .orNull()!!
+        val parentColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "parentColumn")
+                .getAsString("") ?: ""
+
+        val parentField = myFields.firstOrNull {
+            it.columnName == parentColumnInput
+        }
+        if (parentField == null) {
+            context.logger.e(relationElement,
+                    ProcessorErrors.relationCannotFindParentEntityField(
+                            entityName = element.qualifiedName.toString(),
+                            columnName = parentColumnInput,
+                            availableColumns = myFields.map { it.columnName }))
+            return null
+        }
+        // parse it as an entity.
+        val asMember = MoreTypes
+                .asMemberOf(context.processingEnv.typeUtils, container, relationElement)
+        if (asMember.kind == TypeKind.ERROR) {
+            context.logger.e(ProcessorErrors.CANNOT_FIND_TYPE, element)
+            return null
+        }
+        val declared = MoreTypes.asDeclared(asMember)
+        if (!declared.isCollection()) {
+            context.logger.e(relationElement, ProcessorErrors.RELATION_NOT_COLLECTION)
+            return null
+        }
+        val typeArg = declared.typeArguments.first()
+        if (typeArg.kind == TypeKind.ERROR) {
+            context.logger.e(MoreTypes.asTypeElement(typeArg), CANNOT_FIND_TYPE)
+            return null
+        }
+        val typeArgElement = MoreTypes.asTypeElement(typeArg)
+        val entityClassInput = AnnotationMirrors
+                .getAnnotationValue(annotation, "entity").toClassType()
+
+        // do we need to decide on the entity?
+        val inferEntity = (entityClassInput == null
+                || MoreTypes.isTypeOf(Any::class.java, entityClassInput))
+
+        val entity = if (inferEntity) {
+            EntityProcessor(context, typeArgElement, referenceStack).process()
+        } else {
+            EntityProcessor(context, MoreTypes.asTypeElement(entityClassInput),
+                    referenceStack).process()
+        }
+
+        // now find the field in the entity.
+        val entityColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "entityColumn")
+                .getAsString() ?: ""
+        val entityField = entity.fields.firstOrNull {
+            it.columnName == entityColumnInput
+        }
+
+        if (entityField == null) {
+            context.logger.e(relationElement,
+                    ProcessorErrors.relationCannotFindEntityField(
+                            entityName = entity.typeName.toString(),
+                            columnName = entityColumnInput,
+                            availableColumns = entity.fields.map { it.columnName }))
+            return null
+        }
+
+        val field = Field(
+                element = relationElement,
+                name = relationElement.simpleName.toString(),
+                type = context.processingEnv.typeUtils.asMemberOf(container, relationElement),
+                affinity = null,
+                parent = parent)
+
+        val projectionInput = AnnotationMirrors.getAnnotationValue(annotation, "projection")
+                .getAsStringList()
+        val projection = if (projectionInput.isEmpty()) {
+            // we need to infer the projection from inputs.
+            createRelationshipProjection(inferEntity, typeArg, entity, entityField, typeArgElement)
+        } else {
+            // make sure projection makes sense
+            validateRelationshipProjection(projectionInput, entity, relationElement)
+            projectionInput
+        }
+        // if types don't match, row adapter prints a warning
+        return androidx.room.vo.Relation(
+                entity = entity,
+                pojoType = typeArg,
+                field = field,
+                parentField = parentField,
+                entityField = entityField,
+                projection = projection
+        )
+    }
+
+    private fun validateRelationshipProjection(
+            projectionInput: List<String>,
+            entity: Entity,
+            relationElement: VariableElement) {
+        val missingColumns = projectionInput.filterNot { columnName ->
+            entity.fields.any { columnName == it.columnName }
+        }
+        if (missingColumns.isNotEmpty()) {
+            context.logger.e(relationElement,
+                    ProcessorErrors.relationBadProject(entity.typeName.toString(),
+                            missingColumns, entity.fields.map { it.columnName }))
+        }
+    }
+
+    /**
+     * Create the projection column list based on the relationship args.
+     *
+     *  if entity field in the annotation is not specified, it is the method return type
+     *  if it is specified in the annotation:
+     *       still check the method return type, if the same, use it
+     *       if not, check to see if we can find a column Adapter, if so use the childField
+     *       last resort, try to parse it as a pojo to infer it.
+     */
+    private fun createRelationshipProjection(
+            inferEntity: Boolean,
+            typeArg: TypeMirror,
+            entity: Entity,
+            entityField: Field,
+            typeArgElement: TypeElement): List<String> {
+        return if (inferEntity || typeArg.typeName() == entity.typeName) {
+            entity.fields.map { it.columnName }
+        } else {
+            val columnAdapter = context.typeAdapterStore.findCursorValueReader(typeArg, null)
+            if (columnAdapter != null) {
+                // nice, there is a column adapter for this, assume single column response
+                listOf(entityField.name)
+            } else {
+                // last resort, it needs to be a pojo
+                val pojo = PojoProcessor(
+                        baseContext = context,
+                        element = typeArgElement,
+                        bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                        parent = parent,
+                        referenceStack = referenceStack).process()
+                pojo.fields.map { it.columnName }
+            }
+        }
+    }
+
+    private fun detectReferenceRecursion(typeElement: TypeElement): Boolean {
+        if (referenceStack.contains(typeElement.qualifiedName)) {
+            context.logger.e(
+                    typeElement,
+                    ProcessorErrors
+                            .RECURSIVE_REFERENCE_DETECTED
+                            .format(computeReferenceRecursionString(typeElement)))
+            return true
+        }
+        return false
+    }
+
+    private fun computeReferenceRecursionString(typeElement: TypeElement): String {
+        val recursiveTailTypeName = typeElement.qualifiedName
+
+        val referenceRecursionList = mutableListOf<Name>()
+        with(referenceRecursionList) {
+            add(recursiveTailTypeName)
+            addAll(referenceStack.toList().takeLastWhile { it != recursiveTailTypeName })
+            add(recursiveTailTypeName)
+        }
+
+        return referenceRecursionList.joinToString(" -> ")
+    }
+
+    private fun assignGetters(fields: List<Field>, getterCandidates: List<PojoMethod>) {
+        fields.forEach { field ->
+            assignGetter(field, getterCandidates)
+        }
+    }
+
+    private fun assignGetter(field: Field, getterCandidates: List<PojoMethod>) {
+        val success = chooseAssignment(field = field,
+                candidates = getterCandidates,
+                nameVariations = field.getterNameWithVariations,
+                getType = { method ->
+                    method.resolvedType.returnType
+                },
+                assignFromField = {
+                    field.getter = FieldGetter(
+                            name = field.name,
+                            type = field.type,
+                            callType = CallType.FIELD)
+                },
+                assignFromMethod = { match ->
+                    field.getter = FieldGetter(
+                            name = match.name,
+                            type = match.resolvedType.returnType,
+                            callType = CallType.METHOD)
+                },
+                reportAmbiguity = { matching ->
+                    context.logger.e(field.element,
+                            ProcessorErrors.tooManyMatchingGetters(field, matching))
+                })
+        context.checker.check(success, field.element, CANNOT_FIND_GETTER_FOR_FIELD)
+    }
+
+    private fun assignSetters(
+            fields: List<Field>,
+            setterCandidates: List<PojoMethod>,
+            constructor: Constructor?) {
+        fields.forEach { field ->
+            assignSetter(field, setterCandidates, constructor)
+        }
+    }
+
+    private fun assignSetter(
+            field: Field,
+            setterCandidates: List<PojoMethod>,
+            constructor: Constructor?) {
+        if (constructor != null && constructor.hasField(field)) {
+            field.setter = FieldSetter(field.name, field.type, CallType.CONSTRUCTOR)
+            return
+        }
+        val success = chooseAssignment(field = field,
+                candidates = setterCandidates,
+                nameVariations = field.setterNameWithVariations,
+                getType = { method ->
+                    method.resolvedType.parameterTypes.first()
+                },
+                assignFromField = {
+                    field.setter = FieldSetter(
+                            name = field.name,
+                            type = field.type,
+                            callType = CallType.FIELD)
+                },
+                assignFromMethod = { match ->
+                    val paramType = match.resolvedType.parameterTypes.first()
+                    field.setter = FieldSetter(
+                            name = match.name,
+                            type = paramType,
+                            callType = CallType.METHOD)
+                },
+                reportAmbiguity = { matching ->
+                    context.logger.e(field.element,
+                            ProcessorErrors.tooManyMatchingSetter(field, matching))
+                })
+        context.checker.check(success, field.element, CANNOT_FIND_SETTER_FOR_FIELD)
+    }
+
+    /**
+     * Finds a setter/getter from available list of methods.
+     * It returns true if assignment is successful, false otherwise.
+     * At worst case, it sets to the field as if it is accessible so that the rest of the
+     * compilation can continue.
+     */
+    private fun chooseAssignment(
+            field: Field,
+            candidates: List<PojoMethod>,
+            nameVariations: List<String>,
+            getType: (PojoMethod) -> TypeMirror,
+            assignFromField: () -> Unit,
+            assignFromMethod: (PojoMethod) -> Unit,
+            reportAmbiguity: (List<String>) -> Unit
+    ): Boolean {
+        if (field.element.hasAnyOf(PUBLIC)) {
+            assignFromField()
+            return true
+        }
+        val types = context.processingEnv.typeUtils
+
+        val matching = candidates
+                .filter {
+                    // b/69164099
+                    types.isAssignableWithoutVariance(getType(it), field.type)
+                            && (field.nameWithVariations.contains(it.name)
+                            || nameVariations.contains(it.name))
+                }
+                .groupBy {
+                    if (it.element.hasAnyOf(PUBLIC)) PUBLIC else PROTECTED
+                }
+        if (matching.isEmpty()) {
+            // we always assign to avoid NPEs in the rest of the compilation.
+            assignFromField()
+            // if field is not private, assume it works (if we are on the same package).
+            // if not, compiler will tell, we didn't have any better alternative anyways.
+            return !field.element.hasAnyOf(PRIVATE)
+        }
+        val match = verifyAndChooseOneFrom(matching[PUBLIC], reportAmbiguity)
+                ?: verifyAndChooseOneFrom(matching[PROTECTED], reportAmbiguity)
+        if (match == null) {
+            assignFromField()
+            return false
+        } else {
+            assignFromMethod(match)
+            return true
+        }
+    }
+
+    private fun verifyAndChooseOneFrom(
+            candidates: List<PojoMethod>?,
+            reportAmbiguity: (List<String>) -> Unit
+    ): PojoMethod? {
+        if (candidates == null) {
+            return null
+        }
+        if (candidates.size > 1) {
+            reportAmbiguity(candidates.map { it.name })
+        }
+        return candidates.first()
+    }
+
+    private data class FailedConstructor(
+            val method: ExecutableElement,
+            val params: List<String>,
+            val matches: List<Constructor.Param?>
+    ) {
+        fun log(): String {
+            val logPerParam = params.withIndex().joinToString(", ") {
+                "param:${it.value} -> matched field:" + (matches[it.index]?.log() ?: "unmatched")
+            }
+            return "$method -> [$logPerParam]"
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
new file mode 100644
index 0000000..5d9e6b6
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.RawQuery
+import androidx.room.Update
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.vo.CustomTypeConverter
+import androidx.room.vo.Field
+import com.squareup.javapoet.TypeName
+
+object ProcessorErrors {
+    private fun String.trim(): String {
+        return this.trimIndent().replace("\n", " ")
+    }
+    val MISSING_QUERY_ANNOTATION = "Query methods must be annotated with ${Query::class.java}"
+    val MISSING_INSERT_ANNOTATION = "Insertion methods must be annotated with ${Insert::class.java}"
+    val MISSING_DELETE_ANNOTATION = "Deletion methods must be annotated with ${Delete::class.java}"
+    val MISSING_UPDATE_ANNOTATION = "Update methods must be annotated with ${Update::class.java}"
+    val MISSING_RAWQUERY_ANNOTATION = "RawQuery methods must be annotated with" +
+            " ${RawQuery::class.java}"
+    val INVALID_ON_CONFLICT_VALUE = "On conflict value must be one of @OnConflictStrategy values."
+    val INVALID_INSERTION_METHOD_RETURN_TYPE = "Methods annotated with @Insert can return either" +
+            " void, long, Long, long[], Long[] or List<Long>."
+    val TRANSACTION_REFERENCE_DOCS = "https://developer.android.com/reference/android/arch/" +
+            "persistence/room/Transaction.html"
+
+    fun insertionMethodReturnTypeMismatch(definedReturn: TypeName,
+                                          expectedReturnTypes: List<TypeName>): String {
+        return "Method returns $definedReturn but it should return one of the following: `" +
+                expectedReturnTypes.joinToString(", ") + "`. If you want to return the list of" +
+                " row ids from the query, your insertion method can receive only 1 parameter."
+    }
+
+    val ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION = "Abstract method in DAO must be annotated" +
+            " with ${Query::class.java} AND ${Insert::class.java}"
+    val INVALID_ANNOTATION_COUNT_IN_DAO_METHOD = "An abstract DAO method must be" +
+            " annotated with one and only one of the following annotations: " +
+            DaoProcessor.PROCESSED_ANNOTATIONS.joinToString(",") {
+        it.java.simpleName
+    }
+    val CANNOT_RESOLVE_RETURN_TYPE = "Cannot resolve return type for %s"
+    val CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS = "Cannot use unbound generics in query" +
+            " methods. It must be bound to a type through base Dao class."
+    val CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS = "Cannot use unbound generics in" +
+            " insertion methods. It must be bound to a type through base Dao class."
+    val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS = "Cannot use unbound fields in entities."
+    val CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES = "Cannot use unbound generics in Dao classes." +
+            " If you are trying to create a base DAO, create a normal class, extend it with type" +
+            " params then mark the subclass with @Dao."
+    val CANNOT_FIND_GETTER_FOR_FIELD = "Cannot find getter for field."
+    val CANNOT_FIND_SETTER_FOR_FIELD = "Cannot find setter for field."
+    val MISSING_PRIMARY_KEY = "An entity must have at least 1 field annotated with @PrimaryKey"
+    val AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT = "If a primary key is annotated with" +
+            " autoGenerate, its type must be int, Integer, long or Long."
+    val AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS = "When @PrimaryKey annotation is used on a" +
+            " field annotated with @Embedded, the embedded class should have only 1 field."
+
+    fun multiplePrimaryKeyAnnotations(primaryKeys: List<String>): String {
+        return """
+                You cannot have multiple primary keys defined in an Entity. If you
+                want to declare a composite primary key, you should use @Entity#primaryKeys and
+                not use @PrimaryKey. Defined Primary Keys:
+                ${primaryKeys.joinToString(", ")}""".trim()
+    }
+
+    fun primaryKeyColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
+        return "$columnName referenced in the primary key does not exists in the Entity." +
+                " Available column names:${allColumns.joinToString(", ")}"
+    }
+
+    val DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE = "Dao class must be an abstract class or" +
+            " an interface"
+    val DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE = "Database must be annotated with @Database"
+    val DAO_MUST_BE_ANNOTATED_WITH_DAO = "Dao class must be annotated with @Dao"
+
+    fun daoMustHaveMatchingConstructor(daoName: String, dbName: String): String {
+        return """
+                $daoName needs to have either an empty constructor or a constructor that takes
+                $dbName as its only parameter.
+                """.trim()
+    }
+
+    val ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY = "Entity class must be annotated with @Entity"
+    val DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES = "@Database annotation must specify list" +
+            " of entities"
+    val COLUMN_NAME_CANNOT_BE_EMPTY = "Column name cannot be blank. If you don't want to set it" +
+            ", just remove the @ColumnInfo annotation or use @ColumnInfo.INHERIT_FIELD_NAME."
+
+    val ENTITY_TABLE_NAME_CANNOT_BE_EMPTY = "Entity table name cannot be blank. If you don't want" +
+            " to set it, just remove the tableName property."
+
+    val CANNOT_BIND_QUERY_PARAMETER_INTO_STMT = "Query method parameters should either be a" +
+            " type that can be converted into a database column or a List / Array that contains" +
+            " such type. You can consider adding a Type Adapter for this."
+
+    val QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE = "Query/Insert method parameters cannot " +
+            "start with underscore (_)."
+
+    val CANNOT_FIND_QUERY_RESULT_ADAPTER = "Not sure how to convert a Cursor to this method's " +
+            "return type"
+
+    val INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
+            " @Insert but does not have any parameters to insert."
+
+    val DELETION_MISSING_PARAMS = "Method annotated with" +
+            " @Delete but does not have any parameters to delete."
+
+    val UPDATE_MISSING_PARAMS = "Method annotated with" +
+            " @Update but does not have any parameters to update."
+
+    val TRANSACTION_METHOD_MODIFIERS = "Method annotated with @Transaction must not be " +
+            "private, final, or abstract. It can be abstract only if the method is also" +
+            " annotated with @Query."
+
+    val TRANSACTION_MISSING_ON_RELATION = "The return value includes a Pojo with a @Relation." +
+            " It is usually desired to annotate this method with @Transaction to avoid" +
+            " possibility of inconsistent results between the Pojo and its relations. See " +
+            TRANSACTION_REFERENCE_DOCS + " for details."
+
+    val CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER = "Type of the parameter must be a class " +
+            "annotated with @Entity or a collection/array of it."
+
+    val DB_MUST_EXTEND_ROOM_DB = "Classes annotated with @Database should extend " +
+            RoomTypeNames.ROOM_DB
+
+    val LIVE_DATA_QUERY_WITHOUT_SELECT = "LiveData return type can only be used with SELECT" +
+            " queries."
+
+    val OBSERVABLE_QUERY_NOTHING_TO_OBSERVE = "Observable query return type (LiveData, Flowable" +
+            ", DataSource, DataSourceFactory etc) can only be used with SELECT queries that" +
+            " directly or indirectly (via @Relation, for example) access at least one table. For" +
+            " @RawQuery, you should specify the list of tables to be observed via the" +
+            " observedEntities field."
+
+    val RECURSIVE_REFERENCE_DETECTED = "Recursive referencing through @Embedded and/or @Relation " +
+            "detected: %s"
+
+    private val TOO_MANY_MATCHING_GETTERS = "Ambiguous getter for %s. All of the following " +
+            "match: %s. You can @Ignore the ones that you don't want to match."
+
+    fun tooManyMatchingGetters(field: Field, methodNames: List<String>): String {
+        return TOO_MANY_MATCHING_GETTERS.format(field, methodNames.joinToString(", "))
+    }
+
+    private val TOO_MANY_MATCHING_SETTERS = "Ambiguous setter for %s. All of the following " +
+            "match: %s. You can @Ignore the ones that you don't want to match."
+
+    fun tooManyMatchingSetter(field: Field, methodNames: List<String>): String {
+        return TOO_MANY_MATCHING_SETTERS.format(field, methodNames.joinToString(", "))
+    }
+
+    val CANNOT_FIND_COLUMN_TYPE_ADAPTER = "Cannot figure out how to save this field into" +
+            " database. You can consider adding a type converter for it."
+
+    val CANNOT_FIND_STMT_BINDER = "Cannot figure out how to bind this field into a statement."
+
+    val CANNOT_FIND_CURSOR_READER = "Cannot figure out how to read this field from a cursor."
+
+    private val MISSING_PARAMETER_FOR_BIND = "Each bind variable in the query must have a" +
+            " matching method parameter. Cannot find method parameters for %s."
+
+    fun missingParameterForBindVariable(bindVarName: List<String>): String {
+        return MISSING_PARAMETER_FOR_BIND.format(bindVarName.joinToString(", "))
+    }
+
+    private val UNUSED_QUERY_METHOD_PARAMETER = "Unused parameter%s: %s"
+    fun unusedQueryMethodParameter(unusedParams: List<String>): String {
+        return UNUSED_QUERY_METHOD_PARAMETER.format(
+                if (unusedParams.size > 1) "s" else "",
+                unusedParams.joinToString(","))
+    }
+
+    private val DUPLICATE_TABLES = "Table name \"%s\" is used by multiple entities: %s"
+    fun duplicateTableNames(tableName: String, entityNames: List<String>): String {
+        return DUPLICATE_TABLES.format(tableName, entityNames.joinToString(", "))
+    }
+
+    val DELETION_METHODS_MUST_RETURN_VOID_OR_INT = "Deletion methods must either return void or" +
+            " return int (the number of deleted rows)."
+
+    val UPDATE_METHODS_MUST_RETURN_VOID_OR_INT = "Update methods must either return void or" +
+            " return int (the number of updated rows)."
+
+    val DAO_METHOD_CONFLICTS_WITH_OTHERS = "Dao method has conflicts."
+
+    fun duplicateDao(dao: TypeName, methodNames: List<String>): String {
+        return """
+                All of these functions [${methodNames.joinToString(", ")}] return the same DAO
+                class [$dao].
+                A database can use a DAO only once so you should remove ${methodNames.size - 1} of
+                these conflicting DAO methods. If you are implementing any of these to fulfill an
+                interface, don't make it abstract, instead, implement the code that calls the
+                other one.
+                """.trim()
+    }
+
+    fun pojoMissingNonNull(pojoTypeName: TypeName, missingPojoFields: List<String>,
+                           allQueryColumns: List<String>): String {
+        return """
+        The columns returned by the query does not have the fields
+        [${missingPojoFields.joinToString(",")}] in $pojoTypeName even though they are
+        annotated as non-null or primitive.
+        Columns returned by the query: [${allQueryColumns.joinToString(",")}]
+        """.trim()
+    }
+
+    fun cursorPojoMismatch(pojoTypeName: TypeName,
+                           unusedColumns: List<String>, allColumns: List<String>,
+                           unusedFields: List<Field>, allFields: List<Field>): String {
+        val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) {
+            """
+                The query returns some columns [${unusedColumns.joinToString(", ")}] which are not
+                use by $pojoTypeName. You can use @ColumnInfo annotation on the fields to specify
+                the mapping.
+            """.trim()
+        } else {
+            ""
+        }
+        val unusedFieldsWarning = if (unusedFields.isNotEmpty()) {
+            """
+                $pojoTypeName has some fields
+                [${unusedFields.joinToString(", ") { it.columnName }}] which are not returned by the
+                query. If they are not supposed to be read from the result, you can mark them with
+                @Ignore annotation.
+            """.trim()
+        } else {
+            ""
+        }
+        return """
+            $unusedColumnsWarning
+            $unusedFieldsWarning
+            You can suppress this warning by annotating the method with
+            @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH).
+            Columns returned by the query: ${allColumns.joinToString(", ")}.
+            Fields in $pojoTypeName: ${allFields.joinToString(", ") { it.columnName }}.
+            """.trim()
+    }
+
+    val TYPE_CONVERTER_UNBOUND_GENERIC = "Cannot use unbound generics in Type Converters."
+    val TYPE_CONVERTER_BAD_RETURN_TYPE = "Invalid return type for a type converter."
+    val TYPE_CONVERTER_MUST_RECEIVE_1_PARAM = "Type converters must receive 1 parameter."
+    val TYPE_CONVERTER_EMPTY_CLASS = "Class is referenced as a converter but it does not have any" +
+            " converter methods."
+    val TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR = "Classes that are used as TypeConverters must" +
+            " have no-argument public constructors."
+    val TYPE_CONVERTER_MUST_BE_PUBLIC = "Type converters must be public."
+
+    fun duplicateTypeConverters(converters: List<CustomTypeConverter>): String {
+        return "Multiple methods define the same conversion. Conflicts with these:" +
+                " ${converters.joinToString(", ") { it.toString() }}"
+    }
+
+    // TODO must print field paths.
+    val POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME = "Field has non-unique column name."
+
+    fun pojoDuplicateFieldNames(columnName: String, fieldPaths: List<String>): String {
+        return "Multiple fields have the same columnName: $columnName." +
+                " Field names: ${fieldPaths.joinToString(", ")}."
+    }
+
+    fun embeddedPrimaryKeyIsDropped(entityQName: String, fieldName: String): String {
+        return "Primary key constraint on $fieldName is ignored when being merged into " +
+                entityQName
+    }
+
+    val INDEX_COLUMNS_CANNOT_BE_EMPTY = "List of columns in an index cannot be empty"
+
+    fun indexColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
+        return "$columnName referenced in the index does not exists in the Entity." +
+                " Available column names:${allColumns.joinToString(", ")}"
+    }
+
+    fun duplicateIndexInEntity(indexName: String): String {
+        return "There are multiple indices with name $indexName. This happen if you've declared" +
+                " the same index multiple times or different indices have the same name. See" +
+                " @Index documentation for details."
+    }
+
+    fun duplicateIndexInDatabase(indexName: String, indexPaths: List<String>): String {
+        return "There are multiple indices with name $indexName. You should rename " +
+                "${indexPaths.size - 1} of these to avoid the conflict:" +
+                "${indexPaths.joinToString(", ")}."
+    }
+
+    fun droppedEmbeddedFieldIndex(fieldPath: String, grandParent: String): String {
+        return "The index will be dropped when being merged into $grandParent" +
+                "($fieldPath). You must re-declare it in $grandParent if you want to index this" +
+                " field in $grandParent."
+    }
+
+    fun droppedEmbeddedIndex(entityName: String, fieldPath: String, grandParent: String): String {
+        return "Indices defined in $entityName will be dropped when it is merged into" +
+                " $grandParent ($fieldPath). You can re-declare them in $grandParent."
+    }
+
+    fun droppedSuperClassIndex(childEntity: String, superEntity: String): String {
+        return "Indices defined in $superEntity will NOT be re-used in $childEntity. If you want" +
+                " to inherit them, you must re-declare them in $childEntity." +
+                " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
+    }
+
+    fun droppedSuperClassFieldIndex(fieldName: String, childEntity: String,
+                                    superEntity: String): String {
+        return "Index defined on field `$fieldName` in $superEntity will NOT be re-used in" +
+                " $childEntity. " +
+                "If you want to inherit it, you must re-declare it in $childEntity." +
+                " Alternatively, you can set inheritSuperIndices to true in the @Entity annotation."
+    }
+
+    val RELATION_NOT_COLLECTION = "Fields annotated with @Relation must be a List or Set."
+
+    fun relationCannotFindEntityField(entityName: String, columnName: String,
+                                      availableColumns: List<String>): String {
+        return "Cannot find the child entity column `$columnName` in $entityName." +
+                " Options: ${availableColumns.joinToString(", ")}"
+    }
+
+    fun relationCannotFindParentEntityField(entityName: String, columnName: String,
+                                            availableColumns: List<String>): String {
+        return "Cannot find the parent entity column `$columnName` in $entityName." +
+                " Options: ${availableColumns.joinToString(", ")}"
+    }
+
+    val RELATION_IN_ENTITY = "Entities cannot have relations."
+
+    val CANNOT_FIND_TYPE = "Cannot find type."
+
+    fun relationAffinityMismatch(parentColumn: String, childColumn: String,
+                                 parentAffinity: SQLTypeAffinity?,
+                                 childAffinity: SQLTypeAffinity?): String {
+        return """
+        The affinity of parent column ($parentColumn : $parentAffinity) does not match the type
+        affinity of the child column ($childColumn : $childAffinity).
+        """.trim()
+    }
+
+    val CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION = "A field can be annotated with only" +
+            " one of the following:" + PojoProcessor.PROCESSED_ANNOTATIONS.joinToString(",") {
+        it.java.simpleName
+    }
+
+    fun relationBadProject(entityQName: String, missingColumnNames: List<String>,
+                           availableColumnNames: List<String>): String {
+        return """
+        $entityQName does not have the following columns: ${missingColumnNames.joinToString(",")}.
+        Available columns are: ${availableColumnNames.joinToString(",")}
+        """.trim()
+    }
+
+    val MISSING_SCHEMA_EXPORT_DIRECTORY = "Schema export directory is not provided to the" +
+            " annotation processor so we cannot export the schema. You can either provide" +
+            " `room.schemaLocation` annotation processor argument OR set exportSchema to false."
+
+    val INVALID_FOREIGN_KEY_ACTION = "Invalid foreign key action. It must be one of the constants" +
+            " defined in ForeignKey.Action"
+
+    fun foreignKeyNotAnEntity(className: String): String {
+        return """
+        Classes referenced in Foreign Key annotations must be @Entity classes. $className is not
+        an entity
+        """.trim()
+    }
+
+    val FOREIGN_KEY_CANNOT_FIND_PARENT = "Cannot find parent entity class."
+
+    fun foreignKeyChildColumnDoesNotExist(columnName: String, allColumns: List<String>): String {
+        return "($columnName) referenced in the foreign key does not exists in the Entity." +
+                " Available column names:${allColumns.joinToString(", ")}"
+    }
+
+    fun foreignKeyParentColumnDoesNotExist(parentEntity: String,
+                                           missingColumn: String,
+                                           allColumns: List<String>): String {
+        return "($missingColumn) does not exist in $parentEntity. Available columns are" +
+                " ${allColumns.joinToString(",")}"
+    }
+
+    val FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST = "Must specify at least 1 column name for the child"
+
+    val FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST = "Must specify at least 1 column name for the parent"
+
+    fun foreignKeyColumnNumberMismatch(
+            childColumns: List<String>, parentColumns: List<String>): String {
+        return """
+                Number of child columns in foreign key must match number of parent columns.
+                Child reference has ${childColumns.joinToString(",")} and parent reference has
+                ${parentColumns.joinToString(",")}
+               """.trim()
+    }
+
+    fun foreignKeyMissingParentEntityInDatabase(parentTable: String, childEntity: String): String {
+        return """
+                $parentTable table referenced in the foreign keys of $childEntity does not exist in
+                the database. Maybe you forgot to add the referenced entity in the entities list of
+                the @Database annotation?""".trim()
+    }
+
+    fun foreignKeyMissingIndexInParent(parentEntity: String, parentColumns: List<String>,
+                                       childEntity: String, childColumns: List<String>): String {
+        return """
+                $childEntity has a foreign key (${childColumns.joinToString(",")}) that references
+                $parentEntity (${parentColumns.joinToString(",")}) but $parentEntity does not have
+                a unique index on those columns nor the columns are its primary key.
+                SQLite requires having a unique constraint on referenced parent columns so you must
+                add a unique index to $parentEntity that has
+                (${parentColumns.joinToString(",")}) column(s).
+               """.trim()
+    }
+
+    fun foreignKeyMissingIndexInChildColumns(childColumns: List<String>): String {
+        return """
+                (${childColumns.joinToString(",")}) column(s) reference a foreign key but
+                they are not part of an index. This may trigger full table scans whenever parent
+                table is modified so you are highly advised to create an index that covers these
+                columns.
+               """.trim()
+    }
+
+    fun foreignKeyMissingIndexInChildColumn(childColumn: String): String {
+        return """
+                $childColumn column references a foreign key but it is not part of an index. This
+                may trigger full table scans whenever parent table is modified so you are highly
+                advised to create an index that covers this column.
+               """.trim()
+    }
+
+    fun shortcutEntityIsNotInDatabase(database: String, dao: String, entity: String): String {
+        return """
+                $dao is part of $database but this entity is not in the database. Maybe you forgot
+                to add $entity to the entities section of the @Database?
+                """.trim()
+    }
+
+    val MISSING_ROOM_GUAVA_ARTIFACT = "To use Guava features, you must add `guava`" +
+            " artifact from Room as a dependency. androidx.room:guava:<version>"
+
+    val MISSING_ROOM_RXJAVA2_ARTIFACT = "To use RxJava2 features, you must add `rxjava2`" +
+            " artifact from Room as a dependency. androidx.room:rxjava2:<version>"
+
+    fun ambigiousConstructor(
+            pojo: String, paramName: String, matchingFields: List<String>): String {
+        return """
+            Ambiguous constructor. The parameter ($paramName) in $pojo matches multiple fields:
+            [${matchingFields.joinToString(",")}]. If you don't want to use this constructor,
+            you can annotate it with @Ignore. If you want Room to use this constructor, you can
+            rename the parameters to exactly match the field name to fix the ambiguity.
+            """.trim()
+    }
+
+    val MISSING_POJO_CONSTRUCTOR = """
+            Entities and Pojos must have a usable public constructor. You can have an empty
+            constructor or a constructor whose parameters match the fields (by name and type).
+            """.trim()
+
+    val TOO_MANY_POJO_CONSTRUCTORS = """
+            Room cannot pick a constructor since multiple constructors are suitable. Try to annotate
+            unwanted constructors with @Ignore.
+            """.trim()
+
+    val TOO_MANY_POJO_CONSTRUCTORS_CHOOSING_NO_ARG = """
+            There are multiple good constructors and Room will pick the no-arg constructor.
+            You can use the @Ignore annotation to eliminate unwanted constructors.
+            """.trim()
+
+    val RELATION_CANNOT_BE_CONSTRUCTOR_PARAMETER = """
+            Fields annotated with @Relation cannot be constructor parameters. These values are
+            fetched after the object is constructed.
+            """.trim()
+
+    val PAGING_SPECIFY_DATA_SOURCE_TYPE = "For now, Room only supports PositionalDataSource class."
+
+    fun primaryKeyNull(field: String): String {
+        return "You must annotate primary keys with @NonNull. \"$field\" is nullable. SQLite " +
+                "considers this a " +
+                "bug and Room does not allow it. See SQLite docs for details: " +
+                "https://www.sqlite.org/lang_createtable.html"
+    }
+
+    val INVALID_COLUMN_NAME = "Invalid column name. Room does not allow using ` or \" in column" +
+            " names"
+
+    val INVALID_TABLE_NAME = "Invalid table name. Room does not allow using ` or \" in table names"
+
+    val RAW_QUERY_BAD_PARAMS = "RawQuery methods should have 1 and only 1 parameter with type" +
+            " String or SupportSQLiteQuery"
+
+    val RAW_QUERY_BAD_RETURN_TYPE = "RawQuery methods must return a non-void type."
+
+    fun rawQueryBadEntity(typeName: TypeName): String {
+        return """
+            observedEntities field in RawQuery must either reference a class that is annotated
+            with @Entity or it should reference a Pojo that either contains @Embedded fields that
+            are annotated with @Entity or @Relation fields.
+            $typeName does not have these properties, did you mean another class?
+            """.trim()
+    }
+
+    val RAW_QUERY_STRING_PARAMETER_REMOVED = "RawQuery does not allow passing a string anymore." +
+            " Please use ${SupportDbTypeNames.QUERY}."
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
new file mode 100644
index 0000000..6773cac
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.Query
+import androidx.room.SkipQueryVerification
+import androidx.room.Transaction
+import androidx.room.ext.KotlinMetadataProcessor
+import androidx.room.ext.hasAnnotation
+import androidx.room.parser.ParsedQuery
+import androidx.room.parser.QueryType
+import androidx.room.parser.SqlParser
+import androidx.room.solver.query.result.LiveDataQueryResultBinder
+import androidx.room.solver.query.result.PojoRowAdapter
+import androidx.room.verifier.DatabaseVerificaitonErrors
+import androidx.room.verifier.DatabaseVerifier
+import androidx.room.vo.QueryMethod
+import androidx.room.vo.QueryParameter
+import androidx.room.vo.Warning
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
+import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+
+class QueryMethodProcessor(
+        baseContext: Context,
+        val containing: DeclaredType,
+        val executableElement: ExecutableElement,
+        val dbVerifier: DatabaseVerifier? = null
+) : KotlinMetadataProcessor {
+    val context = baseContext.fork(executableElement)
+
+    // for kotlin metadata
+    override val processingEnv: ProcessingEnvironment
+        get() = context.processingEnv
+
+    private val classMetadata =
+            try {
+                containing.asElement().kotlinMetadata
+            } catch (throwable: Throwable) {
+                context.logger.d(executableElement,
+                        "failed to read get kotlin metadata from %s", executableElement)
+            } as? KotlinClassMetadata
+
+    fun process(): QueryMethod {
+        val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
+        val executableType = MoreTypes.asExecutable(asMember)
+
+        val annotation = MoreElements.getAnnotationMirror(executableElement,
+                Query::class.java).orNull()
+        context.checker.check(annotation != null, executableElement,
+                ProcessorErrors.MISSING_QUERY_ANNOTATION)
+
+        val query = if (annotation != null) {
+            val query = SqlParser.parse(
+                    AnnotationMirrors.getAnnotationValue(annotation, "value").value.toString())
+            context.checker.check(query.errors.isEmpty(), executableElement,
+                    query.errors.joinToString("\n"))
+            if (!executableElement.hasAnnotation(SkipQueryVerification::class)) {
+                query.resultInfo = dbVerifier?.analyze(query.original)
+            }
+            if (query.resultInfo?.error != null) {
+                context.logger.e(executableElement,
+                        DatabaseVerificaitonErrors.cannotVerifyQuery(query.resultInfo!!.error!!))
+            }
+
+            context.checker.check(executableType.returnType.kind != TypeKind.ERROR,
+                    executableElement, ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE,
+                    executableElement)
+            query
+        } else {
+            ParsedQuery.MISSING
+        }
+
+        val returnTypeName = TypeName.get(executableType.returnType)
+        context.checker.notUnbound(returnTypeName, executableElement,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
+
+        if (query.type == QueryType.DELETE) {
+            context.checker.check(
+                    returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
+                    executableElement,
+                    ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+            )
+        }
+        val resultBinder = context.typeAdapterStore
+                .findQueryResultBinder(executableType.returnType, query)
+        context.checker.check(resultBinder.adapter != null || query.type != QueryType.SELECT,
+                executableElement, ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
+        if (resultBinder is LiveDataQueryResultBinder) {
+            context.checker.check(query.type == QueryType.SELECT, executableElement,
+                    ProcessorErrors.LIVE_DATA_QUERY_WITHOUT_SELECT)
+        }
+
+        val inTransaction = when (query.type) {
+            QueryType.SELECT -> executableElement.hasAnnotation(Transaction::class)
+            else -> true
+        }
+
+        if (query.type == QueryType.SELECT && !inTransaction) {
+            // put a warning if it is has relations and not annotated w/ transaction
+            resultBinder.adapter?.rowAdapter?.let { rowAdapter ->
+                if (rowAdapter is PojoRowAdapter
+                        && rowAdapter.relationCollectors.isNotEmpty()) {
+                    context.logger.w(Warning.RELATION_QUERY_WITHOUT_TRANSACTION,
+                            executableElement, ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
+                }
+            }
+        }
+        val kotlinParameterNames = classMetadata?.getParameterNames(executableElement)
+
+        val parameters = executableElement.parameters
+                .mapIndexed { index, variableElement ->
+                    QueryParameterProcessor(
+                            baseContext = context,
+                            containing = containing,
+                            element = variableElement,
+                            sqlName = kotlinParameterNames?.getOrNull(index)).process()
+                }
+        val queryMethod = QueryMethod(
+                element = executableElement,
+                query = query,
+                name = executableElement.simpleName.toString(),
+                returnType = executableType.returnType,
+                parameters = parameters,
+                inTransaction = inTransaction,
+                queryResultBinder = resultBinder)
+
+        val missing = queryMethod.sectionToParamMapping
+                .filter { it.second == null }
+                .map { it.first.text }
+        if (missing.isNotEmpty()) {
+            context.logger.e(executableElement,
+                    ProcessorErrors.missingParameterForBindVariable(missing))
+        }
+
+        val unused = queryMethod.parameters.filterNot { param ->
+            queryMethod.sectionToParamMapping.any { it.second == param }
+        }.map(QueryParameter::sqlName)
+
+        if (unused.isNotEmpty()) {
+            context.logger.e(executableElement, ProcessorErrors.unusedQueryMethodParameter(unused))
+        }
+        return queryMethod
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/QueryParameterProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/QueryParameterProcessor.kt
new file mode 100644
index 0000000..13fb3a1
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/QueryParameterProcessor.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.vo.QueryParameter
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.DeclaredType
+
+class QueryParameterProcessor(
+        baseContext: Context,
+        val containing: DeclaredType,
+        val element: VariableElement,
+        private val sqlName: String? = null) {
+    val context = baseContext.fork(element)
+    fun process(): QueryParameter {
+        val asMember = MoreTypes.asMemberOf(context.processingEnv.typeUtils, containing, element)
+        val parameterAdapter = context.typeAdapterStore.findQueryParameterAdapter(asMember)
+        context.checker.check(parameterAdapter != null, element,
+                ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+
+        val name = element.simpleName.toString()
+        context.checker.check(!name.startsWith("_"), element,
+                ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
+        return QueryParameter(
+                name = name,
+                sqlName = sqlName ?: name,
+                type = asMember,
+                queryParamAdapter = parameterAdapter)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
new file mode 100644
index 0000000..128c0f1
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2018 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 androidx.room.processor
+
+import androidx.room.RawQuery
+import androidx.room.Transaction
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.toListOfClassTypes
+import androidx.room.ext.typeName
+import androidx.room.parser.SqlParser
+import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
+import androidx.room.vo.RawQueryMethod
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+class RawQueryMethodProcessor(
+        baseContext: Context,
+        val containing: DeclaredType,
+        val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+    fun process(): RawQueryMethod {
+        val types = context.processingEnv.typeUtils
+        val asMember = types.asMemberOf(containing, executableElement)
+        val executableType = MoreTypes.asExecutable(asMember)
+
+        val annotation = MoreElements.getAnnotationMirror(executableElement,
+                RawQuery::class.java).orNull()
+        context.checker.check(annotation != null, executableElement,
+                ProcessorErrors.MISSING_RAWQUERY_ANNOTATION)
+
+        val returnTypeName = TypeName.get(executableType.returnType)
+        context.checker.notUnbound(returnTypeName, executableElement,
+                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
+        val observedTableNames = processObservedTables()
+        val query = SqlParser.rawQueryForTables(observedTableNames)
+        // build the query but don't calculate result info since we just guessed it.
+        val resultBinder = context.typeAdapterStore
+                .findQueryResultBinder(executableType.returnType, query)
+
+        val runtimeQueryParam = findRuntimeQueryParameter()
+        val inTransaction = executableElement.hasAnnotation(Transaction::class)
+        val rawQueryMethod = RawQueryMethod(
+                element = executableElement,
+                name = executableElement.simpleName.toString(),
+                observedTableNames = observedTableNames,
+                returnType = executableType.returnType,
+                runtimeQueryParam = runtimeQueryParam,
+                inTransaction = inTransaction,
+                queryResultBinder = resultBinder
+        )
+        context.checker.check(rawQueryMethod.returnsValue, executableElement,
+                ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE)
+        return rawQueryMethod
+    }
+
+    private fun processObservedTables(): Set<String> {
+        val annotation = MoreElements
+                .getAnnotationMirror(executableElement,
+                        androidx.room.RawQuery::class.java)
+                .orNull() ?: return emptySet()
+        val entityList = AnnotationMirrors.getAnnotationValue(annotation, "observedEntities")
+        return entityList
+                .toListOfClassTypes()
+                .map {
+                    MoreTypes.asTypeElement(it)
+                }
+                .flatMap {
+                    if (it.hasAnnotation(androidx.room.Entity::class)) {
+                        val entity = EntityProcessor(
+                                baseContext = context,
+                                element = it
+                        ).process()
+                        arrayListOf(entity.tableName)
+                    } else {
+                        val pojo = PojoProcessor(
+                                baseContext = context,
+                                element = it,
+                                bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                                parent = null
+                        ).process()
+                        val tableNames = pojo.accessedTableNames()
+                        // if it is empty, report error as it does not make sense
+                        if (tableNames.isEmpty()) {
+                            context.logger.e(executableElement,
+                                    ProcessorErrors.rawQueryBadEntity(it.asType().typeName()))
+                        }
+                        tableNames
+                    }
+                }.toSet()
+    }
+
+    private fun findRuntimeQueryParameter(): RawQueryMethod.RuntimeQueryParameter? {
+        val types = context.processingEnv.typeUtils
+        if (executableElement.parameters.size == 1 && !executableElement.isVarArgs) {
+            val param = MoreTypes.asMemberOf(
+                    types,
+                    containing,
+                    executableElement.parameters[0])
+            val elementUtils = context.processingEnv.elementUtils
+            val supportQueryType = elementUtils
+                    .getTypeElement(SupportDbTypeNames.QUERY.toString()).asType()
+            val isSupportSql = types.isAssignable(param, supportQueryType)
+            if (isSupportSql) {
+                return RawQueryMethod.RuntimeQueryParameter(
+                        paramName = executableElement.parameters[0].simpleName.toString(),
+                        type = supportQueryType.typeName())
+            }
+            val stringType = elementUtils.getTypeElement("java.lang.String").asType()
+            val isString = types.isAssignable(param, stringType)
+            if (isString) {
+                // special error since this was initially allowed but removed in 1.1 beta1
+                context.logger.e(executableElement, RAW_QUERY_STRING_PARAMETER_REMOVED)
+                return null
+            }
+        }
+        context.logger.e(executableElement, ProcessorErrors.RAW_QUERY_BAD_PARAMS)
+        return null
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
new file mode 100644
index 0000000..45c6455
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.processor
+
+import androidx.room.vo.Entity
+import androidx.room.vo.ShortcutQueryParameter
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+import kotlin.reflect.KClass
+
+/**
+ * Common functionality for shortcut method processors
+ */
+class ShortcutMethodProcessor(baseContext: Context,
+                              val containing: DeclaredType,
+                              val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+    private val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
+    private val executableType = MoreTypes.asExecutable(asMember)
+
+    fun extractAnnotation(klass: KClass<out Annotation>,
+                          errorMsg: String): AnnotationMirror? {
+        val annotation = MoreElements.getAnnotationMirror(executableElement,
+                klass.java).orNull()
+        context.checker.check(annotation != null, executableElement, errorMsg)
+        return annotation
+    }
+
+    fun extractReturnType(): TypeMirror {
+        return executableType.returnType
+    }
+
+    fun extractParams(
+            missingParamError: String
+    ): Pair<Map<String, Entity>, List<ShortcutQueryParameter>> {
+        val params = executableElement.parameters
+                .map { ShortcutParameterProcessor(
+                        baseContext = context,
+                        containing = containing,
+                        element = it).process() }
+        context.checker.check(params.isNotEmpty(), executableElement, missingParamError)
+        val entities = params
+                .filter { it.entityType != null }
+                .associateBy({ it.name }, {
+                    EntityProcessor(
+                            baseContext = context,
+                            element = MoreTypes.asTypeElement(it.entityType)).process()
+                })
+        return Pair(entities, params)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
new file mode 100644
index 0000000..1e7c8c5
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.Entity
+import androidx.room.ext.extendsBound
+import androidx.room.ext.hasAnnotation
+import androidx.room.vo.ShortcutQueryParameter
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
+
+/**
+ * Processes parameters of methods that are annotated with Insert, Delete.
+ */
+class ShortcutParameterProcessor(baseContext: Context,
+                                 val containing: DeclaredType,
+                                 val element: VariableElement) {
+    val context = baseContext.fork(element)
+    fun process(): ShortcutQueryParameter {
+        val asMember = MoreTypes.asMemberOf(context.processingEnv.typeUtils, containing, element)
+        val name = element.simpleName.toString()
+        context.checker.check(!name.startsWith("_"), element,
+                ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
+
+        val (entityType, isMultiple) = extractEntityType(asMember)
+        context.checker.check(entityType != null, element,
+                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER)
+
+        return ShortcutQueryParameter(
+                name = name,
+                type = asMember,
+                entityType = entityType,
+                isMultiple = isMultiple
+        )
+    }
+
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    fun extractEntityType(typeMirror: TypeMirror): Pair<TypeMirror?, Boolean> {
+
+        val elementUtils = context.processingEnv.elementUtils
+        val typeUtils = context.processingEnv.typeUtils
+
+        fun verifyAndPair(entityType: TypeMirror, isMultiple: Boolean): Pair<TypeMirror?, Boolean> {
+            if (!MoreTypes.isType(entityType)) {
+                // kotlin may generate ? extends T so we should reduce it.
+                val boundedVar = entityType.extendsBound()
+                return boundedVar?.let {
+                    verifyAndPair(boundedVar, isMultiple)
+                } ?: Pair(null, isMultiple)
+            }
+            val entityElement = MoreTypes.asElement(entityType)
+            return if (entityElement.hasAnnotation(Entity::class)) {
+                Pair(entityType, isMultiple)
+            } else {
+                Pair(null, isMultiple)
+            }
+        }
+
+        fun extractEntityTypeFromIterator(iterableType: DeclaredType): TypeMirror {
+            ElementFilter.methodsIn(elementUtils
+                    .getAllMembers(typeUtils.asElement(iterableType) as TypeElement)).forEach {
+                if (it.simpleName.toString() == "iterator") {
+                    return MoreTypes.asDeclared(MoreTypes.asExecutable(
+                            typeUtils.asMemberOf(iterableType, it)).returnType)
+                            .typeArguments.first()
+                }
+            }
+            throw IllegalArgumentException("iterator() not found in Iterable $iterableType")
+        }
+
+        val iterableType = typeUtils.erasure(elementUtils
+                .getTypeElement("java.lang.Iterable").asType())
+        if (typeUtils.isAssignable(typeMirror, iterableType)) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            val entity = extractEntityTypeFromIterator(declared)
+            return verifyAndPair(entity, true)
+        }
+        if (typeMirror is ArrayType) {
+            val entity = typeMirror.componentType
+            return verifyAndPair(entity, true)
+        }
+        return verifyAndPair(typeMirror, false)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/SuppressWarningProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/SuppressWarningProcessor.kt
new file mode 100644
index 0000000..eb2cde8
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/SuppressWarningProcessor.kt
@@ -0,0 +1,58 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.vo.Warning
+import com.google.auto.common.AnnotationMirrors
+import com.google.auto.common.MoreElements
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.Element
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
+
+/**
+ * A visitor that reads SuppressWarnings annotations and keeps the ones we know about.
+ */
+object SuppressWarningProcessor {
+
+    fun getSuppressedWarnings(element: Element): Set<Warning> {
+        val annotation = MoreElements.getAnnotationMirror(element,
+                SuppressWarnings::class.java).orNull()
+        return if (annotation == null) {
+            emptySet<Warning>()
+        } else {
+            val value = AnnotationMirrors.getAnnotationValue(annotation, "value")
+            if (value == null) {
+                emptySet<Warning>()
+            } else {
+                VISITOR.visit(value)
+            }
+        }
+    }
+
+    private object VISITOR : SimpleAnnotationValueVisitor6<Set<Warning>, String>() {
+        override fun visitArray(values: List<AnnotationValue>?, elementName: String?
+        ): Set<Warning> {
+            return values?.map {
+                Warning.fromPublicKey(it.value.toString())
+            }?.filterNotNull()?.toSet() ?: emptySet()
+        }
+
+        override fun defaultAction(o: Any?, p: String?): Set<Warning> {
+            return emptySet()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
new file mode 100644
index 0000000..a309e8e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
@@ -0,0 +1,57 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.ext.findKotlinDefaultImpl
+import androidx.room.ext.hasAnyOf
+import androidx.room.vo.TransactionMethod
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier.ABSTRACT
+import javax.lang.model.element.Modifier.DEFAULT
+import javax.lang.model.element.Modifier.FINAL
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.type.DeclaredType
+
+class TransactionMethodProcessor(baseContext: Context,
+                                 val containing: DeclaredType,
+                                 val executableElement: ExecutableElement) {
+
+    val context = baseContext.fork(executableElement)
+
+    fun process(): TransactionMethod {
+        val kotlinDefaultImpl =
+                executableElement.findKotlinDefaultImpl(context.processingEnv.typeUtils)
+        context.checker.check(
+                !executableElement.hasAnyOf(PRIVATE, FINAL)
+                        && (!executableElement.hasAnyOf(ABSTRACT) || kotlinDefaultImpl != null),
+                executableElement, ProcessorErrors.TRANSACTION_METHOD_MODIFIERS)
+
+        val callType = when {
+            executableElement.hasAnyOf(DEFAULT) ->
+                TransactionMethod.CallType.DEFAULT_JAVA8
+            kotlinDefaultImpl != null ->
+                TransactionMethod.CallType.DEFAULT_KOTLIN
+            else ->
+                TransactionMethod.CallType.CONCRETE
+        }
+
+        return TransactionMethod(
+                element = executableElement,
+                name = executableElement.simpleName.toString(),
+                callType = callType)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/UpdateMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/UpdateMethodProcessor.kt
new file mode 100644
index 0000000..f0b9b29
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/UpdateMethodProcessor.kt
@@ -0,0 +1,64 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.OnConflictStrategy.IGNORE
+import androidx.room.OnConflictStrategy.REPLACE
+import androidx.room.Update
+import androidx.room.ext.typeName
+import androidx.room.vo.UpdateMethod
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+class UpdateMethodProcessor(
+        baseContext: Context,
+        val containing: DeclaredType,
+        val executableElement: ExecutableElement) {
+    val context = baseContext.fork(executableElement)
+
+    fun process(): UpdateMethod {
+        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
+        val annotation = delegate
+                .extractAnnotation(Update::class, ProcessorErrors.MISSING_UPDATE_ANNOTATION)
+
+        val onConflict = OnConflictProcessor.extractFrom(annotation)
+        context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE,
+                executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+
+        val returnTypeName = delegate.extractReturnType().typeName()
+        context.checker.check(
+                returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
+                executableElement,
+                ProcessorErrors.UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
+        )
+
+        val (entities, params) = delegate.extractParams(
+                missingParamError = ProcessorErrors
+                        .UPDATE_MISSING_PARAMS
+        )
+
+        return UpdateMethod(
+                element = delegate.executableElement,
+                name = delegate.executableElement.simpleName.toString(),
+                entities = entities,
+                onConflictStrategy = onConflict,
+                returnCount = returnTypeName == TypeName.INT,
+                parameters = params
+        )
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/cache/Cache.kt b/room/compiler/src/main/kotlin/androidx/room/processor/cache/Cache.kt
new file mode 100644
index 0000000..409ad52
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/cache/Cache.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("AddVarianceModifier")
+
+package androidx.room.processor.cache
+
+import androidx.room.processor.FieldProcessor
+import androidx.room.vo.EmbeddedField
+import androidx.room.vo.Entity
+import androidx.room.vo.Pojo
+import androidx.room.vo.Warning
+import java.util.LinkedHashSet
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A cache key can be used to avoid re-processing elements.
+ * <p>
+ * Each context has a cache variable that uses the same backing storage as the Root Context but
+ * adds current adapters and warning suppression list to the key.
+ */
+class Cache(val parent: Cache?, val converters: LinkedHashSet<TypeMirror>,
+            val suppressedWarnings: Set<Warning>) {
+    val entities: Bucket<EntityKey, Entity> = Bucket(parent?.entities)
+    val pojos: Bucket<PojoKey, Pojo> = Bucket(parent?.pojos)
+
+    inner class Bucket<K, T>(source: Bucket<K, T>?) {
+        private val entries: MutableMap<FullKey<K>, T> = source?.entries ?: mutableMapOf()
+        fun get(key: K, calculate: () -> T): T {
+            val fullKey = FullKey(converters, suppressedWarnings, key)
+            return entries.getOrPut(fullKey, {
+                calculate()
+            })
+        }
+    }
+
+    /**
+     * Key for Entity cache
+     */
+    data class EntityKey(val element: Element)
+
+    /**
+     * Key for Pojo cache
+     */
+    data class PojoKey(
+            val element: Element,
+            val scope: FieldProcessor.BindingScope,
+            val parent: EmbeddedField?)
+
+    /**
+     * Internal key representation with adapters & warnings included.
+     * <p>
+     * Converters are kept in a linked set since the order is important for the TypeAdapterStore.
+     */
+    private data class FullKey<T>(
+            val converters: LinkedHashSet<TypeMirror>,
+            val suppressedWarnings: Set<Warning>,
+            val key: T)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/CodeGenScope.kt b/room/compiler/src/main/kotlin/androidx/room/solver/CodeGenScope.kt
new file mode 100644
index 0000000..ca6a11d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/CodeGenScope.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver
+
+import androidx.room.writer.ClassWriter
+import com.google.common.annotations.VisibleForTesting
+import com.squareup.javapoet.CodeBlock
+
+/**
+ * Defines a code generation scope where we can provide temporary variables, global variables etc
+ */
+class CodeGenScope(val writer: ClassWriter) {
+    private var tmpVarIndices = mutableMapOf<String, Int>()
+    private var builder: CodeBlock.Builder? = null
+    companion object {
+        const val TMP_VAR_DEFAULT_PREFIX = "_tmp"
+        const val CLASS_PROPERTY_PREFIX = "__"
+        @VisibleForTesting
+        fun _tmpVar(index: Int) = _tmpVar(TMP_VAR_DEFAULT_PREFIX, index)
+        fun _tmpVar(prefix: String, index: Int) = "$prefix${if (index == 0) "" else "_$index"}"
+    }
+
+    fun builder(): CodeBlock.Builder {
+        if (builder == null) {
+            builder = CodeBlock.builder()
+        }
+        return builder!!
+    }
+
+    fun getTmpVar(): String {
+        return getTmpVar(TMP_VAR_DEFAULT_PREFIX)
+    }
+
+    fun getTmpVar(prefix: String): String {
+        if (!prefix.startsWith("_")) {
+            throw IllegalArgumentException("tmp variable prefixes should start with _")
+        }
+        if (prefix.startsWith(CLASS_PROPERTY_PREFIX)) {
+            throw IllegalArgumentException("cannot use $CLASS_PROPERTY_PREFIX for tmp variables")
+        }
+        val index = tmpVarIndices.getOrElse(prefix) { 0 }
+        val result = _tmpVar(prefix, index)
+        tmpVarIndices.put(prefix, index + 1)
+        return result
+    }
+
+    fun generate() = builder().build().toString()
+
+    /**
+     * copies all variable indices but excludes generated code.
+     */
+    fun fork(): CodeGenScope {
+        val forked = CodeGenScope(writer)
+        forked.tmpVarIndices.putAll(tmpVarIndices)
+        return forked
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/ObservableQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/ObservableQueryResultBinderProvider.kt
new file mode 100644
index 0000000..be38212
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/ObservableQueryResultBinderProvider.kt
@@ -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 androidx.room.solver
+
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.query.result.QueryResultAdapter
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Binder provider class that has common functionality for observables.
+ */
+abstract class ObservableQueryResultBinderProvider(val context: Context)
+    : QueryResultBinderProvider {
+    protected abstract fun extractTypeArg(declared: DeclaredType): TypeMirror
+    protected abstract fun create(typeArg: TypeMirror,
+                                  resultAdapter: QueryResultAdapter?,
+                                  tableNames: Set<String>): QueryResultBinder
+
+    final override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+        val typeArg = extractTypeArg(declared)
+        val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query)
+        val tableNames = ((adapter?.accessedTableNames() ?: emptyList()) +
+                query.tables.map { it.name }).toSet()
+        if (tableNames.isEmpty()) {
+            context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+        }
+        return create(
+                typeArg = typeArg,
+                resultAdapter = adapter,
+                tableNames = tableNames
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/QueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/QueryResultBinderProvider.kt
new file mode 100644
index 0000000..2fda890
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/QueryResultBinderProvider.kt
@@ -0,0 +1,26 @@
+/*
+ * 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 androidx.room.solver
+
+import androidx.room.parser.ParsedQuery
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+
+interface QueryResultBinderProvider {
+    fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder
+    fun matches(declared: DeclaredType): Boolean
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
new file mode 100644
index 0000000..322bf8d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver
+
+import androidx.room.Entity
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.GuavaBaseTypeNames
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.typeName
+import androidx.room.parser.ParsedQuery
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.processor.Context
+import androidx.room.processor.EntityProcessor
+import androidx.room.processor.FieldProcessor
+import androidx.room.processor.PojoProcessor
+import androidx.room.solver.binderprovider.CursorQueryResultBinderProvider
+import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
+import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider
+import androidx.room.solver.binderprovider.FlowableQueryResultBinderProvider
+import androidx.room.solver.binderprovider.GuavaListenableFutureQueryResultBinderProvider
+import androidx.room.solver.binderprovider.InstantQueryResultBinderProvider
+import androidx.room.solver.binderprovider.LiveDataQueryResultBinderProvider
+import androidx.room.solver.binderprovider.RxMaybeQueryResultBinderProvider
+import androidx.room.solver.binderprovider.RxSingleQueryResultBinderProvider
+import androidx.room.solver.query.parameter.ArrayQueryParameterAdapter
+import androidx.room.solver.query.parameter.BasicQueryParameterAdapter
+import androidx.room.solver.query.parameter.CollectionQueryParameterAdapter
+import androidx.room.solver.query.parameter.QueryParameterAdapter
+import androidx.room.solver.query.result.ArrayQueryResultAdapter
+import androidx.room.solver.query.result.EntityRowAdapter
+import androidx.room.solver.query.result.GuavaOptionalQueryResultAdapter
+import androidx.room.solver.query.result.InstantQueryResultBinder
+import androidx.room.solver.query.result.ListQueryResultAdapter
+import androidx.room.solver.query.result.OptionalQueryResultAdapter
+import androidx.room.solver.query.result.PojoRowAdapter
+import androidx.room.solver.query.result.QueryResultAdapter
+import androidx.room.solver.query.result.QueryResultBinder
+import androidx.room.solver.query.result.RowAdapter
+import androidx.room.solver.query.result.SingleColumnRowAdapter
+import androidx.room.solver.query.result.SingleEntityQueryResultAdapter
+import androidx.room.solver.types.BoxedBooleanToBoxedIntConverter
+import androidx.room.solver.types.BoxedPrimitiveColumnTypeAdapter
+import androidx.room.solver.types.ByteArrayColumnTypeAdapter
+import androidx.room.solver.types.ColumnTypeAdapter
+import androidx.room.solver.types.CompositeAdapter
+import androidx.room.solver.types.CompositeTypeConverter
+import androidx.room.solver.types.CursorValueReader
+import androidx.room.solver.types.NoOpConverter
+import androidx.room.solver.types.PrimitiveBooleanToIntConverter
+import androidx.room.solver.types.PrimitiveColumnTypeAdapter
+import androidx.room.solver.types.StatementValueBinder
+import androidx.room.solver.types.StringColumnTypeAdapter
+import androidx.room.solver.types.TypeConverter
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.annotations.VisibleForTesting
+import java.util.LinkedList
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+/**
+ * Holds all type adapters and can create on demand composite type adapters to convert a type into a
+ * database column.
+ */
+class TypeAdapterStore private constructor(
+        val context: Context,
+        /**
+         * first type adapter has the highest priority
+         */
+        private val columnTypeAdapters: List<ColumnTypeAdapter>,
+        /**
+         * first converter has the highest priority
+         */
+        private val typeConverters: List<TypeConverter>) {
+
+    companion object {
+        fun copy(context: Context, store: TypeAdapterStore): TypeAdapterStore {
+            return TypeAdapterStore(context = context,
+                    columnTypeAdapters = store.columnTypeAdapters,
+                    typeConverters = store.typeConverters)
+        }
+
+        fun create(context: Context, @VisibleForTesting vararg extras: Any): TypeAdapterStore {
+            val adapters = arrayListOf<ColumnTypeAdapter>()
+            val converters = arrayListOf<TypeConverter>()
+
+            fun addAny(extra: Any?) {
+                when (extra) {
+                    is TypeConverter -> converters.add(extra)
+                    is ColumnTypeAdapter -> adapters.add(extra)
+                    is List<*> -> extra.forEach(::addAny)
+                    else -> throw IllegalArgumentException("unknown extra $extra")
+                }
+            }
+
+            extras.forEach(::addAny)
+            fun addTypeConverter(converter: TypeConverter) {
+                converters.add(converter)
+            }
+
+            fun addColumnAdapter(adapter: ColumnTypeAdapter) {
+                adapters.add(adapter)
+            }
+
+            val primitives = PrimitiveColumnTypeAdapter
+                    .createPrimitiveAdapters(context.processingEnv)
+            primitives.forEach(::addColumnAdapter)
+            BoxedPrimitiveColumnTypeAdapter
+                    .createBoxedPrimitiveAdapters(context.processingEnv, primitives)
+                    .forEach(::addColumnAdapter)
+            addColumnAdapter(StringColumnTypeAdapter(context.processingEnv))
+            addColumnAdapter(ByteArrayColumnTypeAdapter(context.processingEnv))
+            PrimitiveBooleanToIntConverter.create(context.processingEnv).forEach(::addTypeConverter)
+            BoxedBooleanToBoxedIntConverter.create(context.processingEnv)
+                    .forEach(::addTypeConverter)
+            return TypeAdapterStore(context = context, columnTypeAdapters = adapters,
+                    typeConverters = converters)
+        }
+    }
+
+    val queryResultBinderProviders = listOf(
+            CursorQueryResultBinderProvider(context),
+            LiveDataQueryResultBinderProvider(context),
+            FlowableQueryResultBinderProvider(context),
+            GuavaListenableFutureQueryResultBinderProvider(context),
+            RxMaybeQueryResultBinderProvider(context),
+            RxSingleQueryResultBinderProvider(context),
+            DataSourceQueryResultBinderProvider(context),
+            DataSourceFactoryQueryResultBinderProvider(context),
+            InstantQueryResultBinderProvider(context)
+    )
+
+    // type mirrors that be converted into columns w/o an extra converter
+    private val knownColumnTypeMirrors by lazy {
+        columnTypeAdapters.map { it.out }
+    }
+
+    /**
+     * Searches 1 way to bind a value into a statement.
+     */
+    fun findStatementValueBinder(
+            input: TypeMirror,
+            affinity: SQLTypeAffinity?
+    ): StatementValueBinder? {
+        if (input.kind == TypeKind.ERROR) {
+            return null
+        }
+        val adapter = findDirectAdapterFor(input, affinity)
+        if (adapter != null) {
+            return adapter
+        }
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val binder = findTypeConverter(input, targetTypes) ?: return null
+        // columnAdapter should not be null but we are receiving errors on crash in `first()` so
+        // this safeguard allows us to dispatch the real problem to the user (e.g. why we couldn't
+        // find the right adapter)
+        val columnAdapter = getAllColumnAdapters(binder.to).firstOrNull() ?: return null
+        return CompositeAdapter(input, columnAdapter, binder, null)
+    }
+
+    /**
+     * Returns which entities targets the given affinity.
+     */
+    private fun targetTypeMirrorsFor(affinity: SQLTypeAffinity?): List<TypeMirror> {
+        val specifiedTargets = affinity?.getTypeMirrors(context.processingEnv)
+        return if (specifiedTargets == null || specifiedTargets.isEmpty()) {
+            knownColumnTypeMirrors
+        } else {
+            specifiedTargets
+        }
+    }
+
+    /**
+     * Searches 1 way to read it from cursor
+     */
+    fun findCursorValueReader(output: TypeMirror, affinity: SQLTypeAffinity?): CursorValueReader? {
+        if (output.kind == TypeKind.ERROR) {
+            return null
+        }
+        val adapter = findColumnTypeAdapter(output, affinity)
+        if (adapter != null) {
+            // two way is better
+            return adapter
+        }
+        // we could not find a two way version, search for anything
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val converter = findTypeConverter(targetTypes, output) ?: return null
+        return CompositeAdapter(output,
+                getAllColumnAdapters(converter.from).first(), null, converter)
+    }
+
+    /**
+     * Tries to reverse the converter going through the same nodes, if possible.
+     */
+    @VisibleForTesting
+    fun reverse(converter: TypeConverter): TypeConverter? {
+        return when (converter) {
+            is NoOpConverter -> converter
+            is CompositeTypeConverter -> {
+                val r1 = reverse(converter.conv1) ?: return null
+                val r2 = reverse(converter.conv2) ?: return null
+                CompositeTypeConverter(r2, r1)
+            }
+            else -> {
+                val types = context.processingEnv.typeUtils
+                typeConverters.firstOrNull {
+                    types.isSameType(it.from, converter.to) && types
+                            .isSameType(it.to, converter.from)
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds a two way converter, if you need 1 way, use findStatementValueBinder or
+     * findCursorValueReader.
+     */
+    fun findColumnTypeAdapter(out: TypeMirror, affinity: SQLTypeAffinity?): ColumnTypeAdapter? {
+        if (out.kind == TypeKind.ERROR) {
+            return null
+        }
+        val adapter = findDirectAdapterFor(out, affinity)
+        if (adapter != null) {
+            return adapter
+        }
+        val targetTypes = targetTypeMirrorsFor(affinity)
+        val intoStatement = findTypeConverter(out, targetTypes) ?: return null
+        // ok found a converter, try the reverse now
+        val fromCursor = reverse(intoStatement) ?: findTypeConverter(intoStatement.to, out)
+                ?: return null
+        return CompositeAdapter(out, getAllColumnAdapters(intoStatement.to).first(), intoStatement,
+                fromCursor)
+    }
+
+    private fun findDirectAdapterFor(
+            out: TypeMirror, affinity: SQLTypeAffinity?): ColumnTypeAdapter? {
+        val adapter = getAllColumnAdapters(out).firstOrNull {
+            affinity == null || it.typeAffinity == affinity
+        }
+        return adapter
+    }
+
+    fun findTypeConverter(input: TypeMirror, output: TypeMirror): TypeConverter? {
+        return findTypeConverter(listOf(input), listOf(output))
+    }
+
+    fun findQueryResultBinder(typeMirror: TypeMirror, query: ParsedQuery): QueryResultBinder {
+        return if (typeMirror.kind == TypeKind.DECLARED) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            return queryResultBinderProviders.first {
+                it.matches(declared)
+            }.provide(declared, query)
+        } else {
+            InstantQueryResultBinder(findQueryResultAdapter(typeMirror, query))
+        }
+    }
+
+    fun findQueryResultAdapter(typeMirror: TypeMirror, query: ParsedQuery): QueryResultAdapter? {
+        if (typeMirror.kind == TypeKind.ERROR) {
+            return null
+        }
+        if (typeMirror.kind == TypeKind.DECLARED) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+
+            if (declared.typeArguments.isEmpty()) {
+                val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
+                return SingleEntityQueryResultAdapter(rowAdapter)
+            } else if (
+                    context.processingEnv.typeUtils.erasure(typeMirror).typeName() ==
+                    GuavaBaseTypeNames.OPTIONAL) {
+                // Handle Guava Optional by unpacking its generic type argument and adapting that.
+                // The Optional adapter will reappend the Optional type.
+                val typeArg = declared.typeArguments.first()
+                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
+                return GuavaOptionalQueryResultAdapter(SingleEntityQueryResultAdapter(rowAdapter))
+            } else if (
+                    context.processingEnv.typeUtils.erasure(typeMirror).typeName() ==
+                    CommonTypeNames.OPTIONAL) {
+                // Handle java.util.Optional similarly.
+                val typeArg = declared.typeArguments.first()
+                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
+                return OptionalQueryResultAdapter(SingleEntityQueryResultAdapter(rowAdapter))
+            } else if (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)) {
+                val typeArg = declared.typeArguments.first()
+                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
+                return ListQueryResultAdapter(rowAdapter)
+            }
+            return null
+        } else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
+            val rowAdapter =
+                    findRowAdapter(typeMirror.componentType, query) ?: return null
+            return ArrayQueryResultAdapter(rowAdapter)
+        } else {
+            val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
+            return SingleEntityQueryResultAdapter(rowAdapter)
+        }
+    }
+
+    /**
+     * Find a converter from cursor to the given type mirror.
+     * If there is information about the query result, we try to use it to accept *any* POJO.
+     */
+    @VisibleForTesting
+    fun findRowAdapter(typeMirror: TypeMirror, query: ParsedQuery): RowAdapter? {
+        if (typeMirror.kind == TypeKind.ERROR) {
+            return null
+        }
+        if (typeMirror.kind == TypeKind.DECLARED) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            if (declared.typeArguments.isNotEmpty()) {
+                // TODO one day support this
+                return null
+            }
+            val resultInfo = query.resultInfo
+
+            val (rowAdapter, rowAdapterLogs) = if (resultInfo != null && query.errors.isEmpty()
+                    && resultInfo.error == null) {
+                // if result info is not null, first try a pojo row adapter
+                context.collectLogs { subContext ->
+                    val pojo = PojoProcessor(
+                            baseContext = subContext,
+                            element = MoreTypes.asTypeElement(typeMirror),
+                            bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                            parent = null
+                    ).process()
+                    PojoRowAdapter(
+                            context = subContext,
+                            info = resultInfo,
+                            pojo = pojo,
+                            out = typeMirror)
+                }
+            } else {
+                Pair(null, null)
+            }
+
+            if (rowAdapter == null && query.resultInfo == null) {
+                // we don't know what query returns. Check for entity.
+                val asElement = MoreTypes.asElement(typeMirror)
+                if (asElement.hasAnnotation(Entity::class)) {
+                    return EntityRowAdapter(EntityProcessor(context,
+                            MoreElements.asType(asElement)).process())
+                }
+            }
+
+            if (rowAdapter != null && rowAdapterLogs?.hasErrors() != true) {
+                rowAdapterLogs?.writeTo(context.processingEnv)
+                return rowAdapter
+            }
+
+            if ((resultInfo?.columns?.size ?: 1) == 1) {
+                val singleColumn = findCursorValueReader(typeMirror,
+                        resultInfo?.columns?.get(0)?.type)
+                if (singleColumn != null) {
+                    return SingleColumnRowAdapter(singleColumn)
+                }
+            }
+            // if we tried, return its errors
+            if (rowAdapter != null) {
+                rowAdapterLogs?.writeTo(context.processingEnv)
+                return rowAdapter
+            }
+            if (query.runtimeQueryPlaceholder) {
+                // just go w/ pojo and hope for the best. this happens for @RawQuery where we
+                // try to guess user's intention and hope that their query fits the result.
+                val pojo = PojoProcessor(
+                        baseContext = context,
+                        element = MoreTypes.asTypeElement(typeMirror),
+                        bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                        parent = null
+                ).process()
+                return PojoRowAdapter(
+                        context = context,
+                        info = null,
+                        pojo = pojo,
+                        out = typeMirror)
+            }
+            return null
+        } else {
+            val singleColumn = findCursorValueReader(typeMirror, null) ?: return null
+            return SingleColumnRowAdapter(singleColumn)
+        }
+    }
+
+    fun findQueryParameterAdapter(typeMirror: TypeMirror): QueryParameterAdapter? {
+        if (MoreTypes.isType(typeMirror)
+                && (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)
+                || MoreTypes.isTypeOf(java.util.Set::class.java, typeMirror))) {
+            val declared = MoreTypes.asDeclared(typeMirror)
+            val binder = findStatementValueBinder(declared.typeArguments.first(),
+                    null)
+            if (binder != null) {
+                return CollectionQueryParameterAdapter(binder)
+            } else {
+                // maybe user wants to convert this collection themselves. look for a match
+                val collectionBinder = findStatementValueBinder(typeMirror, null) ?: return null
+                return BasicQueryParameterAdapter(collectionBinder)
+            }
+        } else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
+            val component = typeMirror.componentType
+            val binder = findStatementValueBinder(component, null) ?: return null
+            return ArrayQueryParameterAdapter(binder)
+        } else {
+            val binder = findStatementValueBinder(typeMirror, null) ?: return null
+            return BasicQueryParameterAdapter(binder)
+        }
+    }
+
+    private fun findTypeConverter(input: TypeMirror, outputs: List<TypeMirror>): TypeConverter? {
+        return findTypeConverter(listOf(input), outputs)
+    }
+
+    private fun findTypeConverter(input: List<TypeMirror>, output: TypeMirror): TypeConverter? {
+        return findTypeConverter(input, listOf(output))
+    }
+
+    private fun findTypeConverter(
+            inputs: List<TypeMirror>, outputs: List<TypeMirror>): TypeConverter? {
+        if (inputs.isEmpty()) {
+            return null
+        }
+        val types = context.processingEnv.typeUtils
+        inputs.forEach { input ->
+            if (outputs.any { output -> types.isSameType(input, output) }) {
+                return NoOpConverter(input)
+            }
+        }
+
+        val excludes = arrayListOf<TypeMirror>()
+
+        val queue = LinkedList<TypeConverter>()
+        fun exactMatch(candidates: List<TypeConverter>): TypeConverter? {
+            return candidates.firstOrNull {
+                outputs.any { output -> types.isSameType(output, it.to) }
+            }
+        }
+        inputs.forEach { input ->
+            val candidates = getAllTypeConverters(input, excludes)
+            val match = exactMatch(candidates)
+            if (match != null) {
+                return match
+            }
+            candidates.forEach {
+                excludes.add(it.to)
+                queue.add(it)
+            }
+        }
+        excludes.addAll(inputs)
+        while (queue.isNotEmpty()) {
+            val prev = queue.pop()
+            val from = prev.to
+            val candidates = getAllTypeConverters(from, excludes)
+            val match = exactMatch(candidates)
+            if (match != null) {
+                return CompositeTypeConverter(prev, match)
+            }
+            candidates.forEach {
+                excludes.add(it.to)
+                queue.add(CompositeTypeConverter(prev, it))
+            }
+        }
+        return null
+    }
+
+    private fun getAllColumnAdapters(input: TypeMirror): List<ColumnTypeAdapter> {
+        return columnTypeAdapters.filter {
+            context.processingEnv.typeUtils.isSameType(input, it.out)
+        }
+    }
+
+    /**
+     * Returns all type converters that can receive input type and return into another type.
+     * The returned list is ordered by priority such that if we have an exact match, it is
+     * prioritized.
+     */
+    private fun getAllTypeConverters(input: TypeMirror, excludes: List<TypeMirror>):
+            List<TypeConverter> {
+        val types = context.processingEnv.typeUtils
+        // for input, check assignability because it defines whether we can use the method or not.
+        // for excludes, use exact match
+        return typeConverters.filter { converter ->
+            types.isAssignable(input, converter.from) &&
+                    !excludes.any { types.isSameType(it, converter.to) }
+        }.sortedByDescending {
+                    // if it is the same, prioritize
+                    if (types.isSameType(it.from, input)) {
+                        2
+                    } else {
+                        1
+                    }
+                }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/CursorQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/CursorQueryResultBinderProvider.kt
new file mode 100644
index 0000000..1afe889
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/CursorQueryResultBinderProvider.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.binderprovider
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.solver.QueryResultBinderProvider
+import androidx.room.solver.query.result.CursorQueryResultBinder
+import androidx.room.solver.query.result.QueryResultBinder
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.DeclaredType
+
+class CursorQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
+    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+        return CursorQueryResultBinder()
+    }
+
+    override fun matches(declared: DeclaredType): Boolean =
+        declared.typeArguments.size == 0 && TypeName.get(declared) == AndroidTypeNames.CURSOR
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt
new file mode 100644
index 0000000..be41c6f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/DataSourceFactoryQueryResultBinderProvider.kt
@@ -0,0 +1,63 @@
+/*
+ * 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 androidx.room.solver.binderprovider
+
+import androidx.room.ext.PagingTypeNames
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.QueryResultBinderProvider
+import androidx.room.solver.query.result.DataSourceFactoryQueryResultBinder
+import androidx.room.solver.query.result.ListQueryResultAdapter
+import androidx.room.solver.query.result.PositionalDataSourceQueryResultBinder
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+
+class DataSourceFactoryQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
+    private val dataSourceFactoryTypeMirror: TypeMirror? by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY.toString())?.asType()
+    }
+
+    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+        if (query.tables.isEmpty()) {
+            context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+        }
+        val typeArg = declared.typeArguments[1]
+        val adapter = context.typeAdapterStore.findRowAdapter(typeArg, query)?.let {
+            ListQueryResultAdapter(it)
+        }
+
+        val tableNames = ((adapter?.accessedTableNames() ?: emptyList())
+                + query.tables.map { it.name }).toSet()
+        val countedBinder = PositionalDataSourceQueryResultBinder(adapter, tableNames)
+        return DataSourceFactoryQueryResultBinder(countedBinder)
+    }
+
+    override fun matches(declared: DeclaredType): Boolean =
+            declared.typeArguments.size == 2 && isLivePagedList(declared)
+
+    private fun isLivePagedList(declared: DeclaredType): Boolean {
+        if (dataSourceFactoryTypeMirror == null) {
+            return false
+        }
+        val erasure = context.processingEnv.typeUtils.erasure(declared)
+        // we don't want to return paged list unless explicitly requested
+        return context.processingEnv.typeUtils.isAssignable(dataSourceFactoryTypeMirror, erasure)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt
new file mode 100644
index 0000000..09dbf7c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/DataSourceQueryResultBinderProvider.kt
@@ -0,0 +1,74 @@
+/*
+ * 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 androidx.room.solver.binderprovider
+
+import androidx.room.ext.PagingTypeNames
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.QueryResultBinderProvider
+import androidx.room.solver.query.result.ListQueryResultAdapter
+import androidx.room.solver.query.result.PositionalDataSourceQueryResultBinder
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+
+class DataSourceQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
+    private val dataSourceTypeMirror: TypeMirror? by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(PagingTypeNames.DATA_SOURCE.toString())?.asType()
+    }
+
+    private val positionalDataSourceTypeMirror: TypeMirror? by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(PagingTypeNames.POSITIONAL_DATA_SOURCE.toString())?.asType()
+    }
+
+    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+        if (query.tables.isEmpty()) {
+            context.logger.e(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+        }
+        val typeArg = declared.typeArguments.last()
+        val listAdapter = context.typeAdapterStore.findRowAdapter(typeArg, query)?.let {
+            ListQueryResultAdapter(it)
+        }
+        val tableNames = ((listAdapter?.accessedTableNames() ?: emptyList())
+                + query.tables.map { it.name }).toSet()
+        return PositionalDataSourceQueryResultBinder(listAdapter, tableNames)
+    }
+
+    override fun matches(declared: DeclaredType): Boolean {
+        if (dataSourceTypeMirror == null || positionalDataSourceTypeMirror == null) {
+            return false
+        }
+        if (declared.typeArguments.isEmpty()) {
+            return false
+        }
+        val erasure = context.processingEnv.typeUtils.erasure(declared)
+        val isDataSource = context.processingEnv.typeUtils
+                .isAssignable(erasure, dataSourceTypeMirror)
+        if (!isDataSource) {
+            return false
+        }
+        val isPositional = context.processingEnv.typeUtils
+                .isAssignable(erasure, positionalDataSourceTypeMirror)
+        if (!isPositional) {
+            context.logger.e(ProcessorErrors.PAGING_SPECIFY_DATA_SOURCE_TYPE)
+        }
+        return true
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/FlowableQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/FlowableQueryResultBinderProvider.kt
new file mode 100644
index 0000000..4acd1b8
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/FlowableQueryResultBinderProvider.kt
@@ -0,0 +1,66 @@
+/*
+ * 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 androidx.room.solver.binderprovider
+
+import androidx.room.ext.RoomRxJava2TypeNames
+import androidx.room.ext.RxJava2TypeNames
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.ObservableQueryResultBinderProvider
+import androidx.room.solver.query.result.FlowableQueryResultBinder
+import androidx.room.solver.query.result.QueryResultAdapter
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+
+class FlowableQueryResultBinderProvider(context: Context) :
+        ObservableQueryResultBinderProvider(context) {
+    private val flowableTypeMirror: TypeMirror? by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(RxJava2TypeNames.FLOWABLE.toString())?.asType()
+    }
+
+    private val hasRxJava2Artifact by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(RoomRxJava2TypeNames.RX_ROOM.toString()) != null
+    }
+
+    override fun extractTypeArg(declared: DeclaredType): TypeMirror = declared.typeArguments.first()
+
+    override fun create(typeArg: TypeMirror, resultAdapter: QueryResultAdapter?,
+                        tableNames: Set<String>): QueryResultBinder {
+        return FlowableQueryResultBinder(
+                typeArg = typeArg,
+                queryTableNames = tableNames,
+                adapter = resultAdapter)
+    }
+
+    override fun matches(declared: DeclaredType): Boolean =
+            declared.typeArguments.size == 1 && isRxJava2Publisher(declared)
+
+    private fun isRxJava2Publisher(declared: DeclaredType): Boolean {
+        if (flowableTypeMirror == null) {
+            return false
+        }
+        val erasure = context.processingEnv.typeUtils.erasure(declared)
+        val match = context.processingEnv.typeUtils.isAssignable(flowableTypeMirror, erasure)
+        if (match && !hasRxJava2Artifact) {
+            context.logger.e(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
+        }
+        return match
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/GuavaListenableFutureQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/GuavaListenableFutureQueryResultBinderProvider.kt
new file mode 100644
index 0000000..16db69b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/GuavaListenableFutureQueryResultBinderProvider.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 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 androidx.room.solver.binderprovider
+
+import androidx.room.ext.GuavaUtilConcurrentTypeNames
+import androidx.room.ext.RoomGuavaTypeNames
+import androidx.room.ext.typeName
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.QueryResultBinderProvider
+import androidx.room.solver.query.result.GuavaListenableFutureQueryResultBinder
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+
+class GuavaListenableFutureQueryResultBinderProvider(val context: Context)
+    : QueryResultBinderProvider {
+
+    private val hasGuavaRoom by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(RoomGuavaTypeNames.GUAVA_ROOM.toString()) != null
+    }
+
+    /**
+     * Returns the {@link GuavaListenableFutureQueryResultBinder} instance for the input type, if
+     * possible.
+     *
+     * <p>Emits a compiler error if the Guava Room extension library is not linked.
+     */
+    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+        if (!hasGuavaRoom) {
+            context.logger.e(ProcessorErrors.MISSING_ROOM_GUAVA_ARTIFACT)
+        }
+
+        // Use the type T inside ListenableFuture<T> as the type to adapt and to pass into
+        // the binder.
+        val adapter = context.typeAdapterStore.findQueryResultAdapter(
+                declared.typeArguments.first(), query)
+        return GuavaListenableFutureQueryResultBinder(
+                declared.typeArguments.first(), adapter)
+    }
+
+    /**
+     * Returns true iff the input {@code declared} type is ListenableFuture<T>.
+     */
+    override fun matches(declared: DeclaredType): Boolean =
+        declared.typeArguments.size == 1 &&
+                context.processingEnv.typeUtils.erasure(declared).typeName() ==
+                        GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/InstantQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/InstantQueryResultBinderProvider.kt
new file mode 100644
index 0000000..d0b1660
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/InstantQueryResultBinderProvider.kt
@@ -0,0 +1,33 @@
+/*
+ * 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 androidx.room.solver.binderprovider
+
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.solver.QueryResultBinderProvider
+import androidx.room.solver.query.result.InstantQueryResultBinder
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+
+class InstantQueryResultBinderProvider(val context: Context) : QueryResultBinderProvider {
+    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+        return InstantQueryResultBinder(
+                context.typeAdapterStore.findQueryResultAdapter(declared, query))
+    }
+
+    override fun matches(declared: DeclaredType): Boolean = true
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/LiveDataQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/LiveDataQueryResultBinderProvider.kt
new file mode 100644
index 0000000..1aa2da9
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/LiveDataQueryResultBinderProvider.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.room.solver.binderprovider
+
+import androidx.room.ext.LifecyclesTypeNames
+import androidx.room.processor.Context
+import androidx.room.solver.ObservableQueryResultBinderProvider
+import androidx.room.solver.query.result.LiveDataQueryResultBinder
+import androidx.room.solver.query.result.QueryResultAdapter
+import androidx.room.solver.query.result.QueryResultBinder
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeMirror
+
+class LiveDataQueryResultBinderProvider(context: Context)
+    : ObservableQueryResultBinderProvider(context) {
+    private val liveDataTypeMirror: TypeMirror? by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(LifecyclesTypeNames.LIVE_DATA.toString())?.asType()
+    }
+
+    override fun extractTypeArg(declared: DeclaredType): TypeMirror = declared.typeArguments.first()
+
+    override fun create(typeArg: TypeMirror, resultAdapter: QueryResultAdapter?,
+                        tableNames: Set<String>): QueryResultBinder {
+        return LiveDataQueryResultBinder(
+                typeArg = typeArg,
+                tableNames = tableNames,
+                adapter = resultAdapter)
+    }
+
+    override fun matches(declared: DeclaredType): Boolean =
+            declared.typeArguments.size == 1 && isLiveData(declared)
+
+    private fun isLiveData(declared: DeclaredType): Boolean {
+        if (liveDataTypeMirror == null) {
+            return false
+        }
+        val erasure = context.processingEnv.typeUtils.erasure(declared)
+        return context.processingEnv.typeUtils.isAssignable(liveDataTypeMirror, erasure)
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
new file mode 100644
index 0000000..6ac7d91
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/binderprovider/RxCallableQueryResultBinderProvider.kt
@@ -0,0 +1,60 @@
+/*
+ * 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 androidx.room.solver.binderprovider
+
+import androidx.room.ext.RoomRxJava2TypeNames
+import androidx.room.ext.typeName
+import androidx.room.parser.ParsedQuery
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.QueryResultBinderProvider
+import androidx.room.solver.query.result.QueryResultBinder
+import androidx.room.solver.query.result.RxCallableQueryResultBinder
+import javax.lang.model.type.DeclaredType
+
+sealed class RxCallableQueryResultBinderProvider(val context: Context,
+                                                 val rxType: RxCallableQueryResultBinder.RxType)
+    : QueryResultBinderProvider {
+    private val hasRxJava2Artifact by lazy {
+        context.processingEnv.elementUtils
+                .getTypeElement(RoomRxJava2TypeNames.RX_ROOM.toString()) != null
+    }
+
+    override fun provide(declared: DeclaredType, query: ParsedQuery): QueryResultBinder {
+        val typeArg = declared.typeArguments.first()
+        val adapter = context.typeAdapterStore.findQueryResultAdapter(typeArg, query)
+        return RxCallableQueryResultBinder(rxType, typeArg, adapter)
+    }
+
+    override fun matches(declared: DeclaredType): Boolean =
+            declared.typeArguments.size == 1 && matchesRxType(declared)
+
+    private fun matchesRxType(declared: DeclaredType): Boolean {
+        val erasure = context.processingEnv.typeUtils.erasure(declared)
+        val match = erasure.typeName() == rxType.className
+        if (match && !hasRxJava2Artifact) {
+            context.logger.e(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
+        }
+        return match
+    }
+}
+
+class RxSingleQueryResultBinderProvider(context: Context)
+    : RxCallableQueryResultBinderProvider(context, RxCallableQueryResultBinder.RxType.SINGLE)
+
+class RxMaybeQueryResultBinderProvider(context: Context)
+    : RxCallableQueryResultBinderProvider(context, RxCallableQueryResultBinder.RxType.MAYBE)
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/ArrayQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
new file mode 100644
index 0000000..ea0c788
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/ArrayQueryParameterAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.parameter
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.types.StatementValueBinder
+import com.squareup.javapoet.TypeName
+
+/**
+ * Binds ARRAY(T) (e.g. int[]) into String[] args of a query.
+ */
+class ArrayQueryParameterAdapter(val bindAdapter: StatementValueBinder)
+            : QueryParameterAdapter(true) {
+    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            val itrVar = scope.getTmpVar("_item")
+            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
+                    inputVarName).apply {
+                        bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
+                        addStatement("$L ++", startIndexVarName)
+            }
+            endControlFlow()
+        }
+    }
+
+    override fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("final $T $L = $L.length", TypeName.INT, outputVarName, inputVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/BasicQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/BasicQueryParameterAdapter.kt
new file mode 100644
index 0000000..0ab9bee
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/BasicQueryParameterAdapter.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.parameter
+
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.types.StatementValueBinder
+
+/**
+ * Knows how to convert a query parameter into arguments
+ */
+class BasicQueryParameterAdapter(val bindAdapter: StatementValueBinder)
+            : QueryParameterAdapter(false) {
+    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            bindAdapter.bindToStmt(stmtVarName, startIndexVarName, inputVarName, scope)
+        }
+    }
+
+    override fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        throw UnsupportedOperationException("should not call getArgCount on basic adapters." +
+                "It is always one.")
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/CollectionQueryParameterAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
new file mode 100644
index 0000000..9d3a231
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/CollectionQueryParameterAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.parameter
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.types.StatementValueBinder
+import com.squareup.javapoet.TypeName
+
+/**
+ * Binds Collection<T> (e.g. List<T>) into String[] query args.
+ */
+class CollectionQueryParameterAdapter(val bindAdapter: StatementValueBinder)
+            : QueryParameterAdapter(true) {
+    override fun bindToStmt(inputVarName: String, stmtVarName: String, startIndexVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            val itrVar = scope.getTmpVar("_item")
+            beginControlFlow("for ($T $L : $L)", bindAdapter.typeMirror().typeName(), itrVar,
+                    inputVarName).apply {
+                        bindAdapter.bindToStmt(stmtVarName, startIndexVarName, itrVar, scope)
+                        addStatement("$L ++", startIndexVarName)
+                    }
+            endControlFlow()
+        }
+    }
+
+    override fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("final $T $L = $L.size()", TypeName.INT, outputVarName, inputVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/QueryParameterAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/QueryParameterAdapter.kt
new file mode 100644
index 0000000..3c29ad9
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/parameter/QueryParameterAdapter.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.parameter
+
+import androidx.room.solver.CodeGenScope
+
+/**
+ * Knows how to convert a query parameter into query arguments.
+ */
+abstract class QueryParameterAdapter(val isMultiple: Boolean) {
+    /**
+     * Must bind the value into the statement at the given index.
+     */
+    abstract fun bindToStmt(
+            inputVarName: String,
+            stmtVarName: String,
+            startIndexVarName: String,
+            scope: CodeGenScope)
+
+    /**
+     * Should declare and set the given value with the count
+     */
+    abstract fun getArgCount(inputVarName: String, outputVarName: String, scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
new file mode 100644
index 0000000..86a1c41
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+
+class ArrayQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+    val type = rowAdapter.out
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            rowAdapter?.onCursorReady(cursorVarName, scope)
+
+            val arrayType = ArrayTypeName.of(type.typeName())
+            addStatement("final $T $L = new $T[$L.getCount()]",
+                    arrayType, outVarName, type.typeName(), cursorVarName)
+            val tmpVarName = scope.getTmpVar("_item")
+            val indexVar = scope.getTmpVar("_index")
+            addStatement("$T $L = 0", TypeName.INT, indexVar)
+            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
+                addStatement("final $T $L", type.typeName(), tmpVarName)
+                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                addStatement("$L[$L] = $L", outVarName, indexVar, tmpVarName)
+                addStatement("$L ++", indexVar)
+            }
+            endControlFlow()
+            rowAdapter?.onCursorFinished()?.invoke(scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/BaseObservableQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/BaseObservableQueryResultBinder.kt
new file mode 100644
index 0000000..679a684
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/BaseObservableQueryResultBinder.kt
@@ -0,0 +1,74 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import androidx.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import javax.lang.model.element.Modifier
+
+/**
+ * Base class for query result binders that observe the database. It includes common functionality
+ * like creating a finalizer to release the query or creating the actual adapter call code.
+ */
+abstract class BaseObservableQueryResultBinder(adapter: QueryResultAdapter?)
+    : QueryResultBinder(adapter) {
+
+    protected fun createFinalizeMethod(roomSQLiteQueryVar: String): MethodSpec {
+        return MethodSpec.methodBuilder("finalize").apply {
+            addModifiers(Modifier.PROTECTED)
+            addAnnotation(Override::class.java)
+            addStatement("$L.release()", roomSQLiteQueryVar)
+        }.build()
+    }
+
+    protected fun createRunQueryAndReturnStatements(builder: MethodSpec.Builder,
+                                                    roomSQLiteQueryVar: String,
+                                                    dbField: FieldSpec,
+                                                    inTransaction: Boolean,
+                                                    scope: CodeGenScope) {
+        val transactionWrapper = if (inTransaction) {
+            builder.transactionWrapper(dbField)
+        } else {
+            null
+        }
+        val outVar = scope.getTmpVar("_result")
+        val cursorVar = scope.getTmpVar("_cursor")
+        transactionWrapper?.beginTransactionWithControlFlow()
+        builder.apply {
+            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
+                    DaoWriter.dbField, roomSQLiteQueryVar)
+            beginControlFlow("try").apply {
+                val adapterScope = scope.fork()
+                adapter?.convert(outVar, cursorVar, adapterScope)
+                addCode(adapterScope.builder().build())
+                transactionWrapper?.commitTransaction()
+                addStatement("return $L", outVar)
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$L.close()", cursorVar)
+            }
+            endControlFlow()
+        }
+        transactionWrapper?.endTransactionWithControlFlow()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
new file mode 100644
index 0000000..282e4f5
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CursorQueryResultBinder.kt
@@ -0,0 +1,53 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.FieldSpec
+
+class CursorQueryResultBinder : QueryResultBinder(NO_OP_RESULT_ADAPTER) {
+    override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
+                                  dbField: FieldSpec,
+                                  inTransaction: Boolean,
+                                  scope: CodeGenScope) {
+        val builder = scope.builder()
+        val transactionWrapper = if (inTransaction) {
+            builder.transactionWrapper(dbField)
+        } else {
+            null
+        }
+        transactionWrapper?.beginTransactionWithControlFlow()
+        val resultName = scope.getTmpVar("_tmpResult")
+        builder.addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, resultName,
+                dbField, roomSQLiteQueryVar)
+        transactionWrapper?.commitTransaction()
+        builder.addStatement("return $L", resultName)
+        transactionWrapper?.endTransactionWithControlFlow()
+    }
+
+    companion object {
+        private val NO_OP_RESULT_ADAPTER = object : QueryResultAdapter(null) {
+            override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
new file mode 100644
index 0000000..0f1ecc1
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/DataSourceFactoryQueryResultBinder.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 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 androidx.room.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.PagingTypeNames
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+
+class DataSourceFactoryQueryResultBinder(
+        val positionalDataSourceQueryResultBinder: PositionalDataSourceQueryResultBinder)
+    : QueryResultBinder(positionalDataSourceQueryResultBinder.listAdapter) {
+    @Suppress("HasPlatformType")
+    val typeName = positionalDataSourceQueryResultBinder.itemTypeName
+    override fun convertAndReturn(
+            roomSQLiteQueryVar: String,
+            canReleaseQuery: Boolean,
+            dbField: FieldSpec,
+            inTransaction: Boolean,
+            scope: CodeGenScope
+    ) {
+        scope.builder().apply {
+            val pagedListProvider = TypeSpec
+                    .anonymousClassBuilder("").apply {
+                superclass(ParameterizedTypeName.get(PagingTypeNames.DATA_SOURCE_FACTORY,
+                        Integer::class.typeName(), typeName))
+                addMethod(createCreateMethod(
+                        roomSQLiteQueryVar = roomSQLiteQueryVar,
+                        dbField = dbField,
+                        inTransaction = inTransaction,
+                        scope = scope))
+            }.build()
+            addStatement("return $L", pagedListProvider)
+        }
+    }
+
+    private fun createCreateMethod(
+            roomSQLiteQueryVar: String,
+            dbField: FieldSpec,
+            inTransaction: Boolean,
+            scope: CodeGenScope
+    ): MethodSpec = MethodSpec.methodBuilder("create").apply {
+        addAnnotation(Override::class.java)
+        addModifiers(Modifier.PUBLIC)
+        returns(positionalDataSourceQueryResultBinder.typeName)
+        val countedBinderScope = scope.fork()
+        positionalDataSourceQueryResultBinder.convertAndReturn(
+                roomSQLiteQueryVar = roomSQLiteQueryVar,
+                canReleaseQuery = true,
+                dbField = dbField,
+                inTransaction = inTransaction,
+                scope = countedBinderScope)
+        addCode(countedBinderScope.builder().build())
+    }.build()
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/EntityRowAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/EntityRowAdapter.kt
new file mode 100644
index 0000000..a66f9e1
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/EntityRowAdapter.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.Entity
+import androidx.room.writer.EntityCursorConverterWriter
+import com.squareup.javapoet.MethodSpec
+
+class EntityRowAdapter(val entity: Entity) : RowAdapter(entity.type) {
+    lateinit var methodSpec: MethodSpec
+    override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
+        methodSpec = scope.writer.getOrCreateMethod(EntityCursorConverterWriter(entity))
+    }
+
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $N($L)", outVarName, methodSpec, cursorVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/FlowableQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/FlowableQueryResultBinder.kt
new file mode 100644
index 0000000..08225fc
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/FlowableQueryResultBinder.kt
@@ -0,0 +1,71 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomRxJava2TypeNames
+import androidx.room.ext.T
+import androidx.room.ext.arrayTypeName
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import androidx.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Binds the result as an RxJava2 Flowable.
+ */
+class FlowableQueryResultBinder(val typeArg: TypeMirror, val queryTableNames: Set<String>,
+                                adapter: QueryResultAdapter?)
+    : BaseObservableQueryResultBinder(adapter) {
+    override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
+                                  dbField: FieldSpec,
+                                  inTransaction: Boolean,
+                                  scope: CodeGenScope) {
+        val callableImpl = TypeSpec.anonymousClassBuilder("").apply {
+            val typeName = typeArg.typeName()
+            superclass(ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
+                    typeName))
+            addMethod(MethodSpec.methodBuilder("call").apply {
+                returns(typeName)
+                addException(Exception::class.typeName())
+                addModifiers(Modifier.PUBLIC)
+                addAnnotation(Override::class.java)
+                createRunQueryAndReturnStatements(builder = this,
+                        roomSQLiteQueryVar = roomSQLiteQueryVar,
+                        inTransaction = inTransaction,
+                        dbField = dbField,
+                        scope = scope)
+            }.build())
+            if (canReleaseQuery) {
+                addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+            }
+        }.build()
+        scope.builder().apply {
+            val tableNamesList = queryTableNames.joinToString(",") { "\"$it\"" }
+            addStatement("return $T.createFlowable($N, new $T{$L}, $L)",
+                    RoomRxJava2TypeNames.RX_ROOM, DaoWriter.dbField,
+                    String::class.arrayTypeName(), tableNamesList, callableImpl)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
new file mode 100644
index 0000000..f0cdb4f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2018 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 androidx.room.solver.query.result
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomGuavaTypeNames
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import androidx.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A ResultBinder that emits a ListenableFuture<T> where T is the input {@code typeArg}.
+ *
+ * <p>The Future runs on the background thread Executor.
+ */
+class GuavaListenableFutureQueryResultBinder(
+        val typeArg: TypeMirror,
+        adapter: QueryResultAdapter?)
+    : BaseObservableQueryResultBinder(adapter) {
+
+    override fun convertAndReturn(
+            roomSQLiteQueryVar: String,
+            canReleaseQuery: Boolean,
+            dbField: FieldSpec,
+            inTransaction: Boolean,
+            scope: CodeGenScope) {
+        // Callable<T>
+        val callableImpl = createCallableOfT(
+                roomSQLiteQueryVar,
+                dbField,
+                inTransaction,
+                scope)
+
+        scope.builder().apply {
+            addStatement(
+                    "return $T.createListenableFuture($L, $L, $L)",
+                    RoomGuavaTypeNames.GUAVA_ROOM,
+                    callableImpl,
+                    roomSQLiteQueryVar,
+                    canReleaseQuery)
+        }
+    }
+
+    /**
+     * Returns an anonymous subclass of Callable<T> that executes the database transaction and
+     * constitutes the result T.
+     *
+     * <p>Note that this method does not release the query object.
+     */
+    private fun createCallableOfT(
+            roomSQLiteQueryVar: String,
+            dbField: FieldSpec,
+            inTransaction: Boolean,
+            scope: CodeGenScope): TypeSpec {
+        return TypeSpec.anonymousClassBuilder("").apply {
+            superclass(
+                    ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
+                            typeArg.typeName()))
+            addMethod(
+                    MethodSpec.methodBuilder("call").apply {
+                        // public T call() throws Exception {}
+                        returns(typeArg.typeName())
+                        addAnnotation(Override::class.typeName())
+                        addModifiers(Modifier.PUBLIC)
+                        addException(Exception::class.typeName())
+
+                        // Body.
+                        val transactionWrapper = if (inTransaction) {
+                            transactionWrapper(dbField)
+                        } else {
+                            null
+                        }
+                        transactionWrapper?.beginTransactionWithControlFlow()
+                        apply {
+                            val outVar = scope.getTmpVar("_result")
+                            val cursorVar = scope.getTmpVar("_cursor")
+                            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR,
+                                    cursorVar,
+                                    DaoWriter.dbField, roomSQLiteQueryVar)
+                            beginControlFlow("try").apply {
+                                val adapterScope = scope.fork()
+                                adapter?.convert(outVar, cursorVar, adapterScope)
+                                addCode(adapterScope.builder().build())
+                                transactionWrapper?.commitTransaction()
+                                addStatement("return $L", outVar)
+                            }
+                            nextControlFlow("finally").apply {
+                                addStatement("$L.close()", cursorVar)
+                            }
+                            endControlFlow()
+                        }
+                        transactionWrapper?.endTransactionWithControlFlow()
+                    }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt
new file mode 100644
index 0000000..efdc8b8
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaOptionalQueryResultAdapter.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 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 androidx.room.solver.query.result
+
+import androidx.room.ext.GuavaBaseTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.ParameterizedTypeName
+
+/**
+ * Wraps a row adapter when there is only 1 item in the result, and the result's outer type is
+ * {@link com.google.common.base.Optional}.
+ */
+class GuavaOptionalQueryResultAdapter(private val resultAdapter: SingleEntityQueryResultAdapter)
+    : QueryResultAdapter(resultAdapter.rowAdapter) {
+    val type = resultAdapter.rowAdapter?.out
+    override fun convert(
+            outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            val valueVarName = scope.getTmpVar("_value")
+            resultAdapter?.convert(valueVarName, cursorVarName, scope)
+            addStatement(
+                    "final $T $L = $T.fromNullable($L)",
+                    ParameterizedTypeName.get(GuavaBaseTypeNames.OPTIONAL, type?.typeName()),
+                    outVarName,
+                    GuavaBaseTypeNames.OPTIONAL,
+                    valueVarName)
+        }
+    }
+}
+
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
new file mode 100644
index 0000000..406f41e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/InstantQueryResultBinder.kt
@@ -0,0 +1,61 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import androidx.room.writer.DaoWriter
+import com.squareup.javapoet.FieldSpec
+
+/**
+ * Instantly runs and returns the query.
+ */
+class InstantQueryResultBinder(adapter: QueryResultAdapter?) : QueryResultBinder(adapter) {
+    override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
+                                  dbField: FieldSpec,
+                                  inTransaction: Boolean,
+                                  scope: CodeGenScope) {
+        val transactionWrapper = if (inTransaction) {
+            scope.builder().transactionWrapper(dbField)
+        } else {
+            null
+        }
+        transactionWrapper?.beginTransactionWithControlFlow()
+        scope.builder().apply {
+            val outVar = scope.getTmpVar("_result")
+            val cursorVar = scope.getTmpVar("_cursor")
+            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
+                    DaoWriter.dbField, roomSQLiteQueryVar)
+            beginControlFlow("try").apply {
+                adapter?.convert(outVar, cursorVar, scope)
+                transactionWrapper?.commitTransaction()
+                addStatement("return $L", outVar)
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$L.close()", cursorVar)
+                if (canReleaseQuery) {
+                    addStatement("$L.release()", roomSQLiteQueryVar)
+                }
+            }
+            endControlFlow()
+        }
+        transactionWrapper?.endTransactionWithControlFlow()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
new file mode 100644
index 0000000..a38c431
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ListQueryResultAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import java.util.ArrayList
+
+class ListQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+    val type = rowAdapter.out
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            rowAdapter?.onCursorReady(cursorVarName, scope)
+            val collectionType = ParameterizedTypeName
+                    .get(ClassName.get(List::class.java), type.typeName())
+            val arrayListType = ParameterizedTypeName
+                    .get(ClassName.get(ArrayList::class.java), type.typeName())
+            addStatement("final $T $L = new $T($L.getCount())",
+                    collectionType, outVarName, arrayListType, cursorVarName)
+            val tmpVarName = scope.getTmpVar("_item")
+            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
+                addStatement("final $T $L", type.typeName(), tmpVarName)
+                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                addStatement("$L.add($L)", outVarName, tmpVarName)
+            }
+            endControlFlow()
+            rowAdapter?.onCursorFinished()?.invoke(scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
new file mode 100644
index 0000000..0f2e1c3
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.annotation.NonNull
+import androidx.room.ext.L
+import androidx.room.ext.LifecyclesTypeNames
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.RoomTypeNames.INVALIDATION_OBSERVER
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Converts the query into a LiveData and returns it. No query is run until necessary.
+ */
+class LiveDataQueryResultBinder(val typeArg: TypeMirror, val tableNames: Set<String>,
+                                adapter: QueryResultAdapter?)
+    : BaseObservableQueryResultBinder(adapter) {
+    @Suppress("JoinDeclarationAndAssignment")
+    override fun convertAndReturn(
+            roomSQLiteQueryVar: String,
+            canReleaseQuery: Boolean,
+            dbField: FieldSpec,
+            inTransaction: Boolean,
+            scope: CodeGenScope
+    ) {
+        val typeName = typeArg.typeName()
+
+        val liveDataImpl = TypeSpec.anonymousClassBuilder("").apply {
+            superclass(ParameterizedTypeName.get(LifecyclesTypeNames.COMPUTABLE_LIVE_DATA,
+                    typeName))
+            val observerField = FieldSpec.builder(RoomTypeNames.INVALIDATION_OBSERVER,
+                    scope.getTmpVar("_observer"), Modifier.PRIVATE).build()
+            addField(observerField)
+            addMethod(createComputeMethod(
+                    observerField = observerField,
+                    typeName = typeName,
+                    roomSQLiteQueryVar = roomSQLiteQueryVar,
+                    dbField = dbField,
+                    inTransaction = inTransaction,
+                    scope = scope
+            ))
+            if (canReleaseQuery) {
+                addMethod(createFinalizeMethod(roomSQLiteQueryVar))
+            }
+        }.build()
+        scope.builder().apply {
+            addStatement("return $L.getLiveData()", liveDataImpl)
+        }
+    }
+
+    private fun createComputeMethod(
+            roomSQLiteQueryVar: String,
+            typeName: TypeName,
+            observerField: FieldSpec,
+            dbField: FieldSpec,
+            inTransaction: Boolean,
+            scope: CodeGenScope
+    ): MethodSpec {
+        return MethodSpec.methodBuilder("compute").apply {
+            addAnnotation(Override::class.java)
+            addModifiers(Modifier.PROTECTED)
+            returns(typeName)
+
+            beginControlFlow("if ($N == null)", observerField).apply {
+                addStatement("$N = $L", observerField, createAnonymousObserver())
+                addStatement("$N.getInvalidationTracker().addWeakObserver($N)",
+                        dbField, observerField)
+            }
+            endControlFlow()
+
+            createRunQueryAndReturnStatements(builder = this,
+                    roomSQLiteQueryVar = roomSQLiteQueryVar,
+                    dbField = dbField,
+                    inTransaction = inTransaction,
+                    scope = scope)
+        }.build()
+    }
+
+    private fun createAnonymousObserver(): TypeSpec {
+        val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
+        return TypeSpec.anonymousClassBuilder(tableNamesList).apply {
+            superclass(INVALIDATION_OBSERVER)
+            addMethod(MethodSpec.methodBuilder("onInvalidated").apply {
+                returns(TypeName.VOID)
+                addAnnotation(Override::class.java)
+                addParameter(ParameterSpec.builder(
+                        ParameterizedTypeName.get(Set::class.java, String::class.java), "tables")
+                        .addAnnotation(NonNull::class.java)
+                        .build())
+                addModifiers(Modifier.PUBLIC)
+                addStatement("invalidate()")
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/OptionalQueryResultAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/OptionalQueryResultAdapter.kt
new file mode 100644
index 0000000..2c23867
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/OptionalQueryResultAdapter.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 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 androidx.room.solver.query.result
+
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.ParameterizedTypeName
+
+/**
+ * Wraps a row adapter when there is only 1 item in the result, and the result's outer type is
+ * {@link java.util.Optional}.
+ *
+ * <p>n.b. this will only be useful if the project uses Java 8.
+ */
+class OptionalQueryResultAdapter(private val resultAdapter: SingleEntityQueryResultAdapter)
+    : QueryResultAdapter(resultAdapter.rowAdapter) {
+    val type = resultAdapter.rowAdapter?.out
+    override fun convert(
+            outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            val valueVarName = scope.getTmpVar("_value")
+            resultAdapter?.convert(valueVarName, cursorVarName, scope)
+            addStatement(
+                    "final $T $L = $T.ofNullable($L)",
+                    ParameterizedTypeName.get(CommonTypeNames.OPTIONAL, type?.typeName()),
+                    outVarName,
+                    CommonTypeNames.OPTIONAL,
+                    valueVarName)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
new file mode 100644
index 0000000..a8fe00f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
@@ -0,0 +1,157 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.CodeGenScope
+import androidx.room.verifier.QueryResultInfo
+import androidx.room.vo.Field
+import androidx.room.vo.FieldWithIndex
+import androidx.room.vo.Pojo
+import androidx.room.vo.RelationCollector
+import androidx.room.vo.Warning
+import androidx.room.writer.FieldReadWriteWriter
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Creates the entity from the given info.
+ * <p>
+ * The info comes from the query processor so we know about the order of columns in the result etc.
+ */
+class PojoRowAdapter(
+        context: Context, private val info: QueryResultInfo?,
+        val pojo: Pojo, out: TypeMirror) : RowAdapter(out) {
+    val mapping: Mapping
+    val relationCollectors: List<RelationCollector>
+
+    init {
+        // toMutableList documentation is not clear if it copies so lets be safe.
+        val remainingFields = pojo.fields.mapTo(mutableListOf(), { it })
+        val unusedColumns = arrayListOf<String>()
+        val matchedFields: List<Field>
+        if (info != null) {
+            matchedFields = info.columns.map { column ->
+                // first check remaining, otherwise check any. maybe developer wants to map the same
+                // column into 2 fields. (if they want to post process etc)
+                val field = remainingFields.firstOrNull { it.columnName == column.name } ?:
+                        pojo.fields.firstOrNull { it.columnName == column.name }
+                if (field == null) {
+                    unusedColumns.add(column.name)
+                    null
+                } else {
+                    remainingFields.remove(field)
+                    field
+                }
+            }.filterNotNull()
+            if (unusedColumns.isNotEmpty() || remainingFields.isNotEmpty()) {
+                val warningMsg = ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = pojo.typeName,
+                        unusedColumns = unusedColumns,
+                        allColumns = info.columns.map { it.name },
+                        unusedFields = remainingFields,
+                        allFields = pojo.fields
+                )
+                context.logger.w(Warning.CURSOR_MISMATCH, null, warningMsg)
+            }
+            val nonNulls = remainingFields.filter { it.nonNull }
+            if (nonNulls.isNotEmpty()) {
+                context.logger.e(ProcessorErrors.pojoMissingNonNull(
+                        pojoTypeName = pojo.typeName,
+                        missingPojoFields = nonNulls.map { it.name },
+                        allQueryColumns = info.columns.map { it.name }))
+            }
+            if (matchedFields.isEmpty()) {
+                context.logger.e(ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
+            }
+        } else {
+            matchedFields = remainingFields.map { it }
+            remainingFields.clear()
+        }
+        relationCollectors = RelationCollector.createCollectors(context, pojo.relations)
+
+        mapping = Mapping(
+                matchedFields = matchedFields,
+                unusedColumns = unusedColumns,
+                unusedFields = remainingFields
+        )
+    }
+
+    fun relationTableNames(): List<String> {
+        return relationCollectors.flatMap {
+            val queryTableNames = it.loadAllQuery.tables.map { it.name }
+            if (it.rowAdapter is PojoRowAdapter) {
+                it.rowAdapter.relationTableNames() + queryTableNames
+            } else {
+                queryTableNames
+            }
+        }.distinct()
+    }
+
+    override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
+        relationCollectors.forEach { it.writeInitCode(scope) }
+        mapping.fieldsWithIndices = mapping.matchedFields.map {
+            val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+            val indexMethod = if (info == null) {
+                "getColumnIndex"
+            } else {
+                "getColumnIndexOrThrow"
+            }
+            scope.builder().addStatement("final $T $L = $L.$L($S)",
+                    TypeName.INT, indexVar, cursorVarName, indexMethod, it.columnName)
+            FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = info != null)
+        }
+    }
+
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            FieldReadWriteWriter.readFromCursor(
+                    outVar = outVarName,
+                    outPojo = pojo,
+                    cursorVar = cursorVarName,
+                    fieldsWithIndices = mapping.fieldsWithIndices,
+                    relationCollectors = relationCollectors,
+                    scope = scope)
+        }
+    }
+
+    override fun onCursorFinished(): ((CodeGenScope) -> Unit)? =
+            if (relationCollectors.isEmpty()) {
+                // it is important to return empty to notify that we don't need any post process
+                // task
+                null
+            } else {
+                { scope ->
+                    relationCollectors.forEach { collector ->
+                        collector.writeCollectionCode(scope)
+                    }
+                }
+            }
+
+    data class Mapping(
+            val matchedFields: List<Field>,
+            val unusedColumns: List<String>,
+            val unusedFields: List<Field>) {
+        // set when cursor is ready.
+        lateinit var fieldsWithIndices: List<FieldWithIndex>
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
new file mode 100644
index 0000000..96db613
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PositionalDataSourceQueryResultBinder.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+
+class PositionalDataSourceQueryResultBinder(
+        val listAdapter: ListQueryResultAdapter?,
+        val tableNames: Set<String>) : QueryResultBinder(listAdapter) {
+    val itemTypeName: TypeName = listAdapter?.rowAdapter?.out?.typeName() ?: TypeName.OBJECT
+    val typeName: ParameterizedTypeName = ParameterizedTypeName.get(
+            RoomTypeNames.LIMIT_OFFSET_DATA_SOURCE, itemTypeName)
+    override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
+                                  dbField: FieldSpec,
+                                  inTransaction: Boolean,
+                                  scope: CodeGenScope) {
+        // first comma for table names comes from the string since it might be empty in which case
+        // we don't need a comma. If list is empty, this prevents generating bad code (it is still
+        // an error to have empty list but that is already reported while item is processed)
+        val tableNamesList = tableNames.joinToString("") { ", \"$it\"" }
+        val spec = TypeSpec.anonymousClassBuilder("$N, $L, $L $L",
+                dbField, roomSQLiteQueryVar, inTransaction, tableNamesList).apply {
+            superclass(typeName)
+            addMethod(createConvertRowsMethod(scope))
+        }.build()
+        scope.builder().apply {
+            addStatement("return $L", spec)
+        }
+    }
+
+    private fun createConvertRowsMethod(scope: CodeGenScope): MethodSpec =
+            MethodSpec.methodBuilder("convertRows").apply {
+                addAnnotation(Override::class.java)
+                addModifiers(Modifier.PROTECTED)
+                returns(ParameterizedTypeName.get(CommonTypeNames.LIST, itemTypeName))
+                val cursorParam = ParameterSpec.builder(AndroidTypeNames.CURSOR, "cursor")
+                        .build()
+                addParameter(cursorParam)
+                val resultVar = scope.getTmpVar("_res")
+                val rowsScope = scope.fork()
+                listAdapter?.convert(resultVar, cursorParam.name, rowsScope)
+                addCode(rowsScope.builder().build())
+                addStatement("return $L", resultVar)
+            }.build()
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultAdapter.kt
new file mode 100644
index 0000000..a3be347
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultAdapter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.solver.CodeGenScope
+
+/**
+ * Gets a Cursor and converts it into the return type of a method annotated with @Query.
+ */
+abstract class QueryResultAdapter(val rowAdapter: RowAdapter?) {
+    abstract fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope)
+    fun accessedTableNames(): List<String> {
+        return (rowAdapter as? PojoRowAdapter)?.relationTableNames() ?: emptyList<String>()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
new file mode 100644
index 0000000..71d8139
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/QueryResultBinder.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.FieldSpec
+
+/**
+ * Connects the query, db and the ResultAdapter.
+ * <p>
+ * The default implementation is InstantResultBinder. If the query is deferred rather than executed
+ * directly, such alternative implementations can be implement using this interface (e.g. LiveData,
+ * Rx, caching etc)
+ */
+abstract class QueryResultBinder(val adapter: QueryResultAdapter?) {
+    /**
+     * receives the sql, bind args and adapter and generates the code that runs the query
+     * and returns the result.
+     */
+    abstract fun convertAndReturn(
+            roomSQLiteQueryVar: String,
+            canReleaseQuery: Boolean, // false if query is provided by the user
+            dbField: FieldSpec,
+            inTransaction: Boolean,
+            scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RowAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RowAdapter.kt
new file mode 100644
index 0000000..a9d4db6
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RowAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Converts a row of a cursor result into an Entity or a primitive.
+ * <p>
+ * An instance of this is created for each usage so that it can keep local variables.
+ */
+abstract class RowAdapter(val out: TypeMirror) {
+    /**
+     * Called when cursor variable is ready, good place to put initialization code.
+     */
+    open fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {}
+
+    /**
+     * Called to convert a single row.
+     */
+    abstract fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope)
+
+    /**
+     * Called when the cursor is finished. It is important to return null if no operation is
+     * necessary so that caller can understand that we can do lazy loading.
+     */
+    open fun onCursorFinished(): ((scope: CodeGenScope) -> Unit)? = null
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
new file mode 100644
index 0000000..b94d0c7
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxCallableQueryResultBinder.kt
@@ -0,0 +1,115 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomRxJava2TypeNames
+import androidx.room.ext.RxJava2TypeNames
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Generic Result binder for Rx classes that accept a callable.
+ */
+class RxCallableQueryResultBinder(val rxType: RxType,
+                                  val typeArg: TypeMirror,
+                                  adapter: QueryResultAdapter?)
+    : QueryResultBinder(adapter) {
+    override fun convertAndReturn(roomSQLiteQueryVar: String,
+                                  canReleaseQuery: Boolean,
+                                  dbField: FieldSpec,
+                                  inTransaction: Boolean,
+                                  scope: CodeGenScope) {
+        val callable = TypeSpec.anonymousClassBuilder("").apply {
+            val typeName = typeArg.typeName()
+            superclass(ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
+                    typeName))
+            addMethod(createCallMethod(
+                    roomSQLiteQueryVar = roomSQLiteQueryVar,
+                    canReleaseQuery = canReleaseQuery,
+                    dbField = dbField,
+                    inTransaction = inTransaction,
+                    scope = scope))
+        }.build()
+        scope.builder().apply {
+            addStatement("return $T.fromCallable($L)", rxType.className, callable)
+        }
+    }
+
+    private fun createCallMethod(roomSQLiteQueryVar: String,
+                                 canReleaseQuery: Boolean,
+                                 dbField: FieldSpec,
+                                 inTransaction: Boolean,
+                                 scope: CodeGenScope): MethodSpec {
+        val adapterScope = scope.fork()
+        return MethodSpec.methodBuilder("call").apply {
+            returns(typeArg.typeName())
+            addException(Exception::class.typeName())
+            addModifiers(Modifier.PUBLIC)
+            addAnnotation(Override::class.java)
+            val transactionWrapper = if (inTransaction) {
+                transactionWrapper(dbField)
+            } else {
+                null
+            }
+            transactionWrapper?.beginTransactionWithControlFlow()
+            val outVar = scope.getTmpVar("_result")
+            val cursorVar = scope.getTmpVar("_cursor")
+            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
+                    dbField, roomSQLiteQueryVar)
+            beginControlFlow("try").apply {
+                adapter?.convert(outVar, cursorVar, adapterScope)
+                addCode(adapterScope.generate())
+                if (!rxType.canBeNull) {
+                    beginControlFlow("if($L == null)", outVar).apply {
+                        addStatement("throw new $T($S + $L.getSql())",
+                                RoomRxJava2TypeNames.RX_EMPTY_RESULT_SET_EXCEPTION,
+                                "Query returned empty result set: ",
+                                roomSQLiteQueryVar)
+                    }
+                    endControlFlow()
+                }
+                transactionWrapper?.commitTransaction()
+                addStatement("return $L", outVar)
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$L.close()", cursorVar)
+                if (canReleaseQuery) {
+                    addStatement("$L.release()", roomSQLiteQueryVar)
+                }
+            }
+            endControlFlow()
+            transactionWrapper?.endTransactionWithControlFlow()
+        }.build()
+    }
+
+    enum class RxType(val className: ClassName, val canBeNull: Boolean) {
+        SINGLE(RxJava2TypeNames.SINGLE, canBeNull = false),
+        MAYBE(RxJava2TypeNames.MAYBE, canBeNull = true);
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/SingleColumnRowAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/SingleColumnRowAdapter.kt
new file mode 100644
index 0000000..d06b47a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/SingleColumnRowAdapter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.types.CursorValueReader
+
+/**
+ * Wraps a row adapter when there is only 1 item  with 1 column in the response.
+ */
+class SingleColumnRowAdapter(val reader: CursorValueReader) : RowAdapter(reader.typeMirror()) {
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        reader.readFromCursor(outVarName, cursorVarName, "0", scope)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/SingleEntityQueryResultAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/SingleEntityQueryResultAdapter.kt
new file mode 100644
index 0000000..c6eef30
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/SingleEntityQueryResultAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import defaultValue
+
+/**
+ * Wraps a row adapter when there is only 1 item in the result
+ */
+class SingleEntityQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+    val type = rowAdapter.out
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            rowAdapter?.onCursorReady(cursorVarName, scope)
+            addStatement("final $T $L", type.typeName(), outVarName)
+            beginControlFlow("if($L.moveToFirst())", cursorVarName)
+                rowAdapter?.convert(outVarName, cursorVarName, scope)
+            nextControlFlow("else").apply {
+                addStatement("$L = $L", outVarName, rowAdapter?.out?.defaultValue())
+            }
+            endControlFlow()
+            rowAdapter?.onCursorFinished()?.invoke(scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/TransactionWrapper.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/TransactionWrapper.kt
new file mode 100644
index 0000000..373035b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/TransactionWrapper.kt
@@ -0,0 +1,65 @@
+/*
+ * 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 androidx.room.solver.query.result
+
+import androidx.room.ext.N
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+
+/**
+ * helper class to create correct transaction code.
+ */
+interface TransactionWrapper {
+    fun beginTransactionWithControlFlow()
+    fun commitTransaction()
+    fun endTransactionWithControlFlow()
+}
+
+fun MethodSpec.Builder.transactionWrapper(dbField: FieldSpec) = object : TransactionWrapper {
+    override fun beginTransactionWithControlFlow() {
+        addStatement("$N.beginTransaction()", dbField)
+        beginControlFlow("try")
+    }
+
+    override fun commitTransaction() {
+        addStatement("$N.setTransactionSuccessful()", dbField)
+    }
+
+    override fun endTransactionWithControlFlow() {
+        nextControlFlow("finally")
+        addStatement("$N.endTransaction()", dbField)
+        endControlFlow()
+    }
+}
+
+fun CodeBlock.Builder.transactionWrapper(dbField: FieldSpec) = object : TransactionWrapper {
+    override fun beginTransactionWithControlFlow() {
+        addStatement("$N.beginTransaction()", dbField)
+        beginControlFlow("try")
+    }
+
+    override fun commitTransaction() {
+        addStatement("$N.setTransactionSuccessful()", dbField)
+    }
+
+    override fun endTransactionWithControlFlow() {
+        nextControlFlow("finally")
+        addStatement("$N.endTransaction()", dbField)
+        endControlFlow()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/BoxedBooleanToBoxedIntConverter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
new file mode 100644
index 0000000..a648eac
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+
+/**
+ * int to boolean adapter.
+ */
+object BoxedBooleanToBoxedIntConverter {
+    fun create(processingEnvironment: ProcessingEnvironment): List<TypeConverter> {
+        val tBoolean = processingEnvironment.elementUtils.getTypeElement("java.lang.Boolean")
+                .asType()
+        val tInt = processingEnvironment.elementUtils.getTypeElement("java.lang.Integer")
+                .asType()
+        return listOf(
+                object : TypeConverter(tBoolean, tInt) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L == null ? null : ($L ? 1 : 0)",
+                                outputVarName, inputVarName, inputVarName)
+                    }
+                },
+                object : TypeConverter(tInt, tBoolean) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L == null ? null : $L != 0",
+                                outputVarName, inputVarName, inputVarName)
+                    }
+                }
+        )
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt
new file mode 100644
index 0000000..732260b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/BoxedPrimitiveColumnTypeAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.solver.CodeGenScope
+import com.google.auto.common.MoreTypes
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Adapters for all boxed primitives that has direct cursor mappings.
+ */
+open class BoxedPrimitiveColumnTypeAdapter(
+        boxed: TypeMirror,
+        val primitiveAdapter: PrimitiveColumnTypeAdapter
+) : ColumnTypeAdapter(boxed, primitiveAdapter.typeAffinity) {
+    companion object {
+        fun createBoxedPrimitiveAdapters(
+                processingEnvironment: ProcessingEnvironment,
+                primitiveAdapters: List<PrimitiveColumnTypeAdapter>
+        ): List<ColumnTypeAdapter> {
+
+            return primitiveAdapters.map {
+                BoxedPrimitiveColumnTypeAdapter(
+                        processingEnvironment.typeUtils
+                                .boxedClass(MoreTypes.asPrimitiveType(it.out)).asType(),
+                        it
+                )
+            }
+        }
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L == null)", valueVarName).apply {
+                addStatement("$L.bindNull($L)", stmtName, indexVarName)
+            }
+            nextControlFlow("else").apply {
+                primitiveAdapter.bindToStmt(stmtName, indexVarName, valueVarName, scope)
+            }
+            endControlFlow()
+        }
+    }
+
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L.isNull($L))", cursorVarName, indexVarName).apply {
+                addStatement("$L = null", outVarName)
+            }
+            nextControlFlow("else").apply {
+                primitiveAdapter.readFromCursor(outVarName, cursorVarName, indexVarName, scope)
+            }
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteArrayColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteArrayColumnTypeAdapter.kt
new file mode 100644
index 0000000..4a7b230
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteArrayColumnTypeAdapter.kt
@@ -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 androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind
+
+class ByteArrayColumnTypeAdapter(env: ProcessingEnvironment) : ColumnTypeAdapter(
+        out = env.typeUtils.getArrayType(env.typeUtils.getPrimitiveType(TypeKind.BYTE)),
+        typeAffinity = SQLTypeAffinity.BLOB) {
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L.getBlob($L)", outVarName, cursorVarName, indexVarName)
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L == null)", valueVarName)
+                    .addStatement("$L.bindNull($L)", stmtName, indexVarName)
+            nextControlFlow("else")
+                    .addStatement("$L.bindBlob($L, $L)", stmtName, indexVarName, valueVarName)
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/ColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/ColumnTypeAdapter.kt
new file mode 100644
index 0000000..e8833bf
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/ColumnTypeAdapter.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.parser.SQLTypeAffinity
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A code generator that can read a field from Cursor and write a field to a Statement
+ */
+abstract class ColumnTypeAdapter(val out: TypeMirror, val typeAffinity: SQLTypeAffinity) :
+        StatementValueBinder, CursorValueReader {
+    val outTypeName: TypeName by lazy { TypeName.get(out) }
+    override fun typeMirror() = out
+    override fun affinity(): SQLTypeAffinity = typeAffinity
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/CompositeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/CompositeAdapter.kt
new file mode 100644
index 0000000..c285f79
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/CompositeAdapter.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A column adapter that uses a type converter to do the conversion. The type converter may be
+ * a composite one.
+ */
+class CompositeAdapter(out: TypeMirror, val columnTypeAdapter: ColumnTypeAdapter,
+                       val intoStatementConverter: TypeConverter?,
+                       val fromCursorConverter: TypeConverter?)
+    : ColumnTypeAdapter(out, columnTypeAdapter.typeAffinity) {
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        if (fromCursorConverter == null) {
+            return
+        }
+        scope.builder().apply {
+            val tmpCursorValue = scope.getTmpVar()
+            addStatement("final $T $L", columnTypeAdapter.outTypeName, tmpCursorValue)
+            columnTypeAdapter.readFromCursor(tmpCursorValue, cursorVarName, indexVarName, scope)
+            fromCursorConverter.convert(tmpCursorValue, outVarName, scope)
+        }
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        if (intoStatementConverter == null) {
+            return
+        }
+        scope.builder().apply {
+            val tmpVar = scope.getTmpVar()
+            addStatement("final $T $L", columnTypeAdapter.out, tmpVar)
+            intoStatementConverter.convert(valueVarName, tmpVar, scope)
+            columnTypeAdapter.bindToStmt(stmtName, indexVarName, tmpVar, scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/CompositeTypeConverter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/CompositeTypeConverter.kt
new file mode 100644
index 0000000..e233b50
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/CompositeTypeConverter.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+
+/**
+ * combines 2 type converters
+ */
+class CompositeTypeConverter(val conv1: TypeConverter, val conv2: TypeConverter) : TypeConverter(
+        conv1.from, conv2.to) {
+    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            val tmp = scope.getTmpVar()
+            addStatement("final $T $L", conv1.to.typeName(), tmp)
+            conv1.convert(inputVarName, tmp, scope)
+            conv2.convert(tmp, outputVarName, scope)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/CursorValueReader.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/CursorValueReader.kt
new file mode 100644
index 0000000..30d6b6f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/CursorValueReader.kt
@@ -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 androidx.room.solver.types
+
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Reads value from a cursor at the given index.
+ * see: StatementValueBinder
+ */
+interface CursorValueReader {
+    fun affinity(): SQLTypeAffinity
+    fun typeMirror(): TypeMirror
+    fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/CustomTypeConverterWrapper.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/CustomTypeConverterWrapper.kt
new file mode 100644
index 0000000..5d174c0
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/CustomTypeConverterWrapper.kt
@@ -0,0 +1,64 @@
+/*
+ * 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 androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.CustomTypeConverter
+import androidx.room.writer.ClassWriter
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import javax.lang.model.element.Modifier
+
+/**
+ * Wraps a type converter specified by the developer and forwards calls to it.
+ */
+class CustomTypeConverterWrapper(val custom: CustomTypeConverter)
+    : TypeConverter(custom.from, custom.to) {
+
+    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            if (custom.isStatic) {
+                addStatement("$L = $T.$L($L)",
+                        outputVarName, custom.typeName,
+                        custom.methodName, inputVarName)
+            } else {
+                addStatement("$L = $N.$L($L)",
+                        outputVarName, typeConverter(scope),
+                        custom.methodName, inputVarName)
+            }
+        }
+    }
+
+    fun typeConverter(scope: CodeGenScope): FieldSpec {
+        val baseName = (custom.typeName as ClassName).simpleName().decapitalize()
+        return scope.writer.getOrCreateField(object : ClassWriter.SharedFieldSpec(
+                baseName, custom.typeName) {
+            override fun getUniqueKey(): String {
+                return "converter_${custom.typeName}"
+            }
+
+            override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+                builder.addModifiers(Modifier.PRIVATE)
+                builder.addModifiers(Modifier.FINAL)
+                builder.initializer("new $T()", custom.typeName)
+            }
+        })
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/NoOpConverter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/NoOpConverter.kt
new file mode 100644
index 0000000..352843c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/NoOpConverter.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Yes, we need this when user input is the same as the desired output.
+ * <p>
+ * Each query parameter receives an adapter that converts it into a String (or String[]). This
+ * TypeAdapter basically serves as a wrapper for converting String parameter into the String[] of
+ * the query. Not having this would require us to special case handle String, String[], List<String>
+ * etc.
+ */
+class NoOpConverter(type: TypeMirror) : TypeConverter(
+        type, type) {
+    override fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L", outputVarName, inputVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveBooleanToIntConverter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveBooleanToIntConverter.kt
new file mode 100644
index 0000000..b8f63d9
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveBooleanToIntConverter.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind.BOOLEAN
+import javax.lang.model.type.TypeKind.INT
+
+/**
+ * int to boolean adapter.
+ */
+object PrimitiveBooleanToIntConverter {
+    fun create(processingEnvironment: ProcessingEnvironment): List<TypeConverter> {
+        val tBoolean = processingEnvironment.typeUtils.getPrimitiveType(BOOLEAN)
+        val tInt = processingEnvironment.typeUtils.getPrimitiveType(INT)
+        return listOf(
+                object : TypeConverter(tBoolean, tInt) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L ? 1 : 0", outputVarName, inputVarName)
+                    }
+                },
+                object : TypeConverter(tInt, tBoolean) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().addStatement("$L = $L != 0", outputVarName, inputVarName)
+                    }
+                })
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveColumnTypeAdapter.kt
new file mode 100644
index 0000000..8ff759c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveColumnTypeAdapter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.ext.typeName
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.parser.SQLTypeAffinity.REAL
+import androidx.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind.BYTE
+import javax.lang.model.type.TypeKind.CHAR
+import javax.lang.model.type.TypeKind.DOUBLE
+import javax.lang.model.type.TypeKind.FLOAT
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.SHORT
+
+/**
+ * Adapters for all primitives that has direct cursor mappings.
+ */
+open class PrimitiveColumnTypeAdapter(out: PrimitiveType,
+                                      val cursorGetter: String,
+                                      val stmtSetter: String,
+                                      typeAffinity: SQLTypeAffinity)
+        : ColumnTypeAdapter(out, typeAffinity) {
+    val cast = if (cursorGetter == "get${out.typeName().toString().capitalize()}")
+                    ""
+                else
+                    "(${out.typeName()}) "
+
+    companion object {
+        fun createPrimitiveAdapters(
+                processingEnvironment: ProcessingEnvironment
+        ): List<PrimitiveColumnTypeAdapter> {
+            return listOf(
+                    Triple(INT, "getInt", "bindLong"),
+                    Triple(SHORT, "getShort", "bindLong"),
+                    Triple(BYTE, "getShort", "bindLong"),
+                    Triple(LONG, "getLong", "bindLong"),
+                    Triple(CHAR, "getInt", "bindLong"),
+                    Triple(FLOAT, "getFloat", "bindDouble"),
+                    Triple(DOUBLE, "getDouble", "bindDouble")
+            ).map {
+                PrimitiveColumnTypeAdapter(
+                        out = processingEnvironment.typeUtils.getPrimitiveType(it.first),
+                        cursorGetter = it.second,
+                        stmtSetter = it.third,
+                        typeAffinity = when (it.first) {
+                            INT, SHORT, BYTE, LONG, CHAR -> SQLTypeAffinity.INTEGER
+                            FLOAT, DOUBLE -> REAL
+                            else -> throw IllegalArgumentException("invalid type")
+                        }
+                )
+            }
+        }
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L.$L($L, $L)", stmtName, stmtSetter, indexVarName, valueVarName)
+    }
+
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L$L.$L($L)", outVarName, cast, cursorVarName,
+                        cursorGetter, indexVarName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/StatementValueBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/StatementValueBinder.kt
new file mode 100644
index 0000000..995ca98
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/StatementValueBinder.kt
@@ -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 androidx.room.solver.types
+
+import androidx.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Binds a value into a statement
+ * see: CursorValueReader
+ */
+interface StatementValueBinder {
+    fun typeMirror(): TypeMirror
+    fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/StringColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/StringColumnTypeAdapter.kt
new file mode 100644
index 0000000..5b15699
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/StringColumnTypeAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.ext.L
+import androidx.room.parser.SQLTypeAffinity.TEXT
+import androidx.room.solver.CodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+
+class StringColumnTypeAdapter(processingEnvironment: ProcessingEnvironment)
+    : ColumnTypeAdapter((processingEnvironment.elementUtils.getTypeElement(
+        String::class.java.canonicalName)).asType(), TEXT) {
+    override fun readFromCursor(outVarName: String, cursorVarName: String, indexVarName: String,
+                                scope: CodeGenScope) {
+        scope.builder()
+                .addStatement("$L = $L.getString($L)", outVarName, cursorVarName, indexVarName)
+    }
+
+    override fun bindToStmt(stmtName: String, indexVarName: String, valueVarName: String,
+                            scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if ($L == null)", valueVarName)
+                    .addStatement("$L.bindNull($L)", stmtName, indexVarName)
+            nextControlFlow("else")
+                    .addStatement("$L.bindString($L, $L)", stmtName, indexVarName, valueVarName)
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/TypeConverter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/TypeConverter.kt
new file mode 100644
index 0000000..f967135
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/TypeConverter.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.types
+
+import androidx.room.solver.CodeGenScope
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A code generator that can convert from 1 type to another
+ */
+abstract class TypeConverter(val from: TypeMirror, val to: TypeMirror) {
+    abstract fun convert(inputVarName: String, outputVarName: String, scope: CodeGenScope)
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/verifier/ColumnInfo.kt b/room/compiler/src/main/kotlin/androidx/room/verifier/ColumnInfo.kt
new file mode 100644
index 0000000..788beaf
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/verifier/ColumnInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * 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 androidx.room.verifier
+
+import androidx.room.parser.SQLTypeAffinity
+
+/**
+ * Represents a column in a query response
+ */
+data class ColumnInfo(val name: String, val type: SQLTypeAffinity)
diff --git a/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerificaitonErrors.kt b/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerificaitonErrors.kt
new file mode 100644
index 0000000..c9b7394
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerificaitonErrors.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 androidx.room.verifier
+
+import java.sql.SQLException
+
+object DatabaseVerificaitonErrors {
+    private val CANNOT_CREATE_TABLE: String = "Create table statement had an error: %s"
+    fun cannotCreateTable(exception: SQLException): String {
+        return CANNOT_CREATE_TABLE.format(exception.message)
+    }
+
+    private val CANNOT_VERIFY_QUERY: String = "There is a problem with the query: %s"
+    fun cannotVerifyQuery(exception: SQLException): String {
+        return CANNOT_VERIFY_QUERY.format(exception.message)
+    }
+
+    private val CANNOT_CREATE_SQLITE_CONNECTION: String = "Room cannot create an SQLite" +
+            " connection to verify the queries. Query verification will be disabled. Error: %s"
+    fun cannotCreateConnection(exception: Exception): String {
+        return CANNOT_CREATE_SQLITE_CONNECTION.format(exception.message)
+    }
+
+    val CANNOT_GET_TMP_JAVA_DIR = "Cannot read tmp java dir which is necessary to load sqlite" +
+            " lib. Database SQL verification will be disabled"
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerifier.kt b/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerifier.kt
new file mode 100644
index 0000000..cadc44b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/verifier/DatabaseVerifier.kt
@@ -0,0 +1,125 @@
+/*
+ * 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 androidx.room.verifier
+
+import androidx.room.processor.Context
+import androidx.room.vo.Entity
+import androidx.room.vo.Warning
+import columnInfo
+import org.sqlite.JDBC
+import java.io.File
+import java.sql.Connection
+import java.sql.DriverManager
+import java.sql.SQLException
+import java.util.UUID
+import java.util.regex.Pattern
+import javax.lang.model.element.Element
+
+/**
+ * Builds an in-memory version of the database and verifies the queries against it.
+ * This class is also used to resolve the return types.
+ */
+class DatabaseVerifier private constructor(
+        val connection: Connection, val context: Context, val entities: List<Entity>) {
+    companion object {
+        private const val CONNECTION_URL = "jdbc:sqlite::memory:"
+        /**
+         * Taken from:
+         * https://github.com/robolectric/robolectric/blob/master/shadows/framework/
+         * src/main/java/org/robolectric/shadows/ShadowSQLiteConnection.java#L94
+         *
+         * This is actually not accurate because it might swap anything since it does not parse
+         * SQL. That being said, for the verification purposes, it does not matter and clearly
+         * much easier than parsing and rebuilding the query.
+         */
+        private val COLLATE_LOCALIZED_UNICODE_PATTERN = Pattern.compile(
+                "\\s+COLLATE\\s+(LOCALIZED|UNICODE)", Pattern.CASE_INSENSITIVE)
+
+        init {
+            // see: https://github.com/xerial/sqlite-jdbc/issues/97
+            val tmpDir = System.getProperty("java.io.tmpdir")
+            if (tmpDir != null) {
+                val outDir = File(tmpDir, "room-${UUID.randomUUID()}")
+                outDir.mkdirs()
+                outDir.deleteOnExit()
+                System.setProperty("org.sqlite.tmpdir", outDir.absolutePath)
+                // dummy call to trigger JDBC initialization so that we can unregister it
+                JDBC.isValidURL(CONNECTION_URL)
+                unregisterDrivers()
+            }
+        }
+
+        /**
+         * Tries to create a verifier but returns null if it cannot find the driver.
+         */
+        fun create(context: Context, element: Element, entities: List<Entity>): DatabaseVerifier? {
+            return try {
+                val connection = JDBC.createConnection(CONNECTION_URL, java.util.Properties())
+                DatabaseVerifier(connection, context, entities)
+            } catch (ex: Exception) {
+                context.logger.w(Warning.CANNOT_CREATE_VERIFICATION_DATABASE, element,
+                        DatabaseVerificaitonErrors.cannotCreateConnection(ex))
+                null
+            }
+        }
+
+        /**
+         * Unregisters the JDBC driver. If we don't do this, we'll leak the driver which leaks a
+         * whole class loader.
+         * see: https://github.com/xerial/sqlite-jdbc/issues/267
+         * see: https://issuetracker.google.com/issues/62473121
+         */
+        private fun unregisterDrivers() {
+            try {
+                DriverManager.getDriver(CONNECTION_URL)?.let {
+                    DriverManager.deregisterDriver(it)
+                }
+            } catch (t: Throwable) {
+                System.err.println("Room: cannot unregister driver ${t.message}")
+            }
+        }
+    }
+    init {
+        entities.forEach { entity ->
+            val stmt = connection.createStatement()
+            stmt.executeUpdate(stripLocalizeCollations(entity.createTableQuery))
+        }
+    }
+
+    fun analyze(sql: String): QueryResultInfo {
+        return try {
+            val stmt = connection.prepareStatement(stripLocalizeCollations(sql))
+            QueryResultInfo(stmt.columnInfo())
+        } catch (ex: SQLException) {
+            QueryResultInfo(emptyList(), ex)
+        }
+    }
+
+    private fun stripLocalizeCollations(sql: String) =
+        COLLATE_LOCALIZED_UNICODE_PATTERN.matcher(sql).replaceAll(" COLLATE NOCASE")
+
+    fun closeConnection(context: Context) {
+        if (!connection.isClosed) {
+            try {
+                connection.close()
+            } catch (t: Throwable) {
+                //ignore.
+                context.logger.d("failed to close the database connection ${t.message}")
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/verifier/QueryResultInfo.kt b/room/compiler/src/main/kotlin/androidx/room/verifier/QueryResultInfo.kt
new file mode 100644
index 0000000..a8c9476
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/verifier/QueryResultInfo.kt
@@ -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 androidx.room.verifier
+
+import java.sql.SQLException
+
+/**
+ * Represents the result of a query.
+ * <p>
+ * This information is obtained by preparing the query against an in memory database at compile
+ * time.
+ */
+data class QueryResultInfo(val columns: List<ColumnInfo>, val error: SQLException? = null)
diff --git a/room/compiler/src/main/kotlin/androidx/room/verifier/jdbc_ext.kt b/room/compiler/src/main/kotlin/androidx/room/verifier/jdbc_ext.kt
new file mode 100644
index 0000000..df2ee65
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/verifier/jdbc_ext.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.verifier.ColumnInfo
+import java.sql.PreparedStatement
+import java.sql.ResultSet
+import java.sql.ResultSetMetaData
+import java.sql.SQLException
+
+internal fun <T> ResultSet.collect(f: (ResultSet) -> T): List<T> {
+    val result = arrayListOf<T>()
+    try {
+        while (next()) {
+            result.add(f.invoke(this))
+        }
+    } finally {
+        close()
+    }
+    return result
+}
+
+private fun <T> PreparedStatement.map(f: (Int, ResultSetMetaData) -> T): List<T> {
+    val columnCount = try {
+        metaData.columnCount
+    } catch (ex: SQLException) {
+        // ignore, no-result query
+        0
+    }
+    // return is separate than data creation because we want to know who throws the exception
+    return (1.rangeTo(columnCount)).map { f(it, metaData) }
+}
+
+internal fun PreparedStatement.columnNames(): List<String> {
+    return map { index, data -> data.getColumnName(index) }
+}
+
+private fun PreparedStatement.tryGetAffinity(columnIndex: Int): SQLTypeAffinity {
+    return try {
+        SQLTypeAffinity.valueOf(metaData.getColumnTypeName(columnIndex).capitalize())
+    } catch (ex: IllegalArgumentException) {
+        SQLTypeAffinity.NULL
+    }
+}
+
+internal fun PreparedStatement.columnInfo(): List<ColumnInfo> {
+    //see: http://sqlite.1065341.n5.nabble.com/Column-order-in-resultset-td23127.html
+    return map { index, data -> ColumnInfo(data.getColumnName(index), tryGetAffinity(index)) }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/CallType.kt b/room/compiler/src/main/kotlin/androidx/room/vo/CallType.kt
new file mode 100644
index 0000000..e81a41b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/CallType.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+enum class CallType {
+    FIELD,
+    METHOD,
+    CONSTRUCTOR
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Constructor.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Constructor.kt
new file mode 100644
index 0000000..fdab508
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Constructor.kt
@@ -0,0 +1,53 @@
+/*
+ * 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 androidx.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * For each Entity / Pojo we process has a constructor. It might be the empty constructor or a
+ * constructor with fields.
+ */
+data class Constructor(val element: ExecutableElement, val params: List<Param>) {
+
+    fun hasField(field: Field): Boolean {
+        return params.any {
+            when (it) {
+                is FieldParam -> it.field === field
+                is EmbeddedParam -> it.embedded.field === field
+                else -> false
+            }
+        }
+    }
+
+    class FieldParam(val field: Field) : Param(ParamType.FIELD) {
+        override fun log(): String = field.getPath()
+    }
+
+    class EmbeddedParam(val embedded: EmbeddedField) : Param(ParamType.EMBEDDED) {
+        override fun log(): String = embedded.field.getPath()
+    }
+
+    abstract class Param(val type: ParamType) {
+        abstract fun log(): String
+    }
+
+    enum class ParamType {
+        FIELD,
+        EMBEDDED
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/CustomTypeConverter.kt b/room/compiler/src/main/kotlin/androidx/room/vo/CustomTypeConverter.kt
new file mode 100644
index 0000000..e1f16d7
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/CustomTypeConverter.kt
@@ -0,0 +1,37 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.ext.hasAnyOf
+import androidx.room.ext.typeName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Generated when we parse a method annotated with TypeConverter.
+ */
+data class CustomTypeConverter(val type: TypeMirror,
+                               val method: ExecutableElement,
+                               val from: TypeMirror, val to: TypeMirror) {
+    val typeName: TypeName by lazy { type.typeName() }
+    val fromTypeName: TypeName by lazy { from.typeName() }
+    val toTypeName: TypeName by lazy { to.typeName() }
+    val methodName by lazy { method.simpleName.toString() }
+    val isStatic by lazy { method.hasAnyOf(Modifier.STATIC) }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Dao.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Dao.kt
new file mode 100644
index 0000000..e12fa0e
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Dao.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+data class Dao(
+        val element: TypeElement, val type: DeclaredType,
+        val queryMethods: List<QueryMethod>,
+        val rawQueryMethods: List<RawQueryMethod>,
+        val insertionMethods: List<InsertionMethod>,
+        val deletionMethods: List<DeletionMethod>,
+        val updateMethods: List<UpdateMethod>,
+        val transactionMethods: List<TransactionMethod>,
+        val constructorParamType: TypeName?) {
+    // parsed dao might have a suffix if it is used in multiple databases.
+    private var suffix: String? = null
+
+    fun setSuffix(newSuffix: String) {
+        if (this.suffix != null) {
+            throw IllegalStateException("cannot set suffix twice")
+        }
+        this.suffix = if (newSuffix == "") "" else "_$newSuffix"
+    }
+
+    val typeName: ClassName by lazy { ClassName.get(element) }
+
+    val shortcutMethods: List<ShortcutMethod> by lazy {
+        deletionMethods + updateMethods
+    }
+
+    private val implClassName by lazy {
+        if (suffix == null) {
+            suffix = ""
+        }
+        val path = arrayListOf<String>()
+        var enclosing = element.enclosingElement
+        while (enclosing is TypeElement) {
+            path.add(ClassName.get(enclosing as TypeElement).simpleName())
+            enclosing = enclosing.enclosingElement
+        }
+        path.reversed().joinToString("_") + "${typeName.simpleName()}${suffix}_Impl"
+    }
+
+    val implTypeName: ClassName by lazy {
+        ClassName.get(typeName.packageName(), implClassName)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/DaoMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/DaoMethod.kt
new file mode 100644
index 0000000..9266294
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/DaoMethod.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import javax.lang.model.element.Element
+
+/**
+ * References a method that returns a dao in a Database
+ */
+data class DaoMethod(val element: Element, val name: String, val dao: Dao)
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
new file mode 100644
index 0000000..e79a901
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Database.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.RoomMasterTable
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.SchemaBundle
+import com.squareup.javapoet.ClassName
+import org.apache.commons.codec.digest.DigestUtils
+import java.io.File
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Holds information about a class annotated with Database.
+ */
+data class Database(val element: TypeElement,
+                    val type: TypeMirror,
+                    val entities: List<Entity>,
+                    val daoMethods: List<DaoMethod>,
+                    val version: Int,
+                    val exportSchema: Boolean,
+                    val enableForeignKeys: Boolean) {
+    val typeName: ClassName by lazy { ClassName.get(element) }
+
+    private val implClassName by lazy {
+        "${typeName.simpleNames().joinToString("_")}_Impl"
+    }
+
+    val implTypeName: ClassName by lazy {
+        ClassName.get(typeName.packageName(), implClassName)
+    }
+
+    val bundle by lazy {
+        DatabaseBundle(version, identityHash, entities.map(Entity::toBundle),
+                listOf(RoomMasterTable.CREATE_QUERY,
+                        RoomMasterTable.createInsertQuery(identityHash)))
+    }
+
+    /**
+     * Create a has that identifies this database definition so that at runtime we can check to
+     * ensure developer didn't forget to update the version.
+     */
+    val identityHash: String by lazy {
+        val idKey = SchemaIdentityKey()
+        idKey.appendSorted(entities)
+        idKey.hash()
+    }
+
+    val legacyIdentityHash: String by lazy {
+        val entityDescriptions = entities
+                .sortedBy { it.tableName }
+                .map { it.createTableQuery }
+        val indexDescriptions = entities
+                .flatMap { entity ->
+                    entity.indices.map { index ->
+                        index.createQuery(entity.tableName)
+                    }
+                }
+        val input = (entityDescriptions + indexDescriptions).joinToString("¯\\_(ツ)_/¯")
+        DigestUtils.md5Hex(input)
+    }
+
+    fun exportSchema(file: File) {
+        val schemaBundle = SchemaBundle(SchemaBundle.LATEST_FORMAT, bundle)
+        if (file.exists()) {
+            val existing = file.inputStream().use {
+                SchemaBundle.deserialize(it)
+            }
+            if (existing.isSchemaEqual(schemaBundle)) {
+                return
+            }
+        }
+        SchemaBundle.serialize(schemaBundle, file)
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt
new file mode 100644
index 0000000..890367f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/DeletionMethod.kt
@@ -0,0 +1,25 @@
+/*
+ * 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 androidx.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+class DeletionMethod(
+        element: ExecutableElement,
+        name: String,
+        entities: Map<String, Entity>, returnCount: Boolean,
+        parameters: List<ShortcutQueryParameter>
+) : ShortcutMethod(element, name, entities, returnCount, parameters)
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/EmbeddedField.kt b/room/compiler/src/main/kotlin/androidx/room/vo/EmbeddedField.kt
new file mode 100644
index 0000000..e505396
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/EmbeddedField.kt
@@ -0,0 +1,49 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.annotation.NonNull
+import androidx.room.ext.hasAnnotation
+
+/**
+ * Used when a field is embedded inside an Entity or Pojo.
+ */
+// used in cache matching, must stay as a data class or implement equals
+data class EmbeddedField(val field: Field, val prefix: String = "",
+                         val parent: EmbeddedField?) {
+    val getter by lazy { field.getter }
+    val setter by lazy { field.setter }
+    val nonNull = field.element.hasAnnotation(NonNull::class)
+    lateinit var pojo: Pojo
+    val mRootParent: EmbeddedField by lazy {
+        parent?.mRootParent ?: this
+    }
+
+    fun isDescendantOf(other: EmbeddedField): Boolean {
+        if (parent == other) {
+            return true
+        } else if (parent == null) {
+            return false
+        } else {
+            return parent.isDescendantOf(other)
+        }
+    }
+
+    fun isNonNullRecursively(): Boolean {
+        return field.nonNull && (parent == null || parent.isNonNullRecursively())
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Entity.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Entity.kt
new file mode 100644
index 0000000..631a924
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Entity.kt
@@ -0,0 +1,99 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.migration.bundle.BundleUtil
+import androidx.room.migration.bundle.EntityBundle
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+// TODO make data class when move to kotlin 1.1
+class Entity(
+        element: TypeElement, val tableName: String, type: DeclaredType,
+        fields: List<Field>, embeddedFields: List<EmbeddedField>,
+        val primaryKey: PrimaryKey, val indices: List<Index>,
+        val foreignKeys: List<ForeignKey>,
+        constructor: Constructor?)
+    : Pojo(element, type, fields, embeddedFields, emptyList(), constructor), HasSchemaIdentity {
+
+    val createTableQuery by lazy {
+        createTableQuery(tableName)
+    }
+
+    // a string defining the identity of this entity, which can be used for equality checks
+    override fun getIdKey(): String {
+        val identityKey = SchemaIdentityKey()
+        identityKey.append(tableName)
+        identityKey.append(primaryKey)
+        identityKey.appendSorted(fields)
+        identityKey.appendSorted(indices)
+        identityKey.appendSorted(foreignKeys)
+        return identityKey.hash()
+    }
+
+    private fun createTableQuery(tableName: String): String {
+        val definitions = (fields.map {
+            val autoIncrement = primaryKey.autoGenerateId && primaryKey.fields.contains(it)
+            it.databaseDefinition(autoIncrement)
+        } + createPrimaryKeyDefinition() + createForeignKeyDefinitions()).filterNotNull()
+        return "CREATE TABLE IF NOT EXISTS `$tableName` (${definitions.joinToString(", ")})"
+    }
+
+    private fun createForeignKeyDefinitions(): List<String> {
+        return foreignKeys.map { it.databaseDefinition() }
+    }
+
+    private fun createPrimaryKeyDefinition(): String? {
+        return if (primaryKey.fields.isEmpty() || primaryKey.autoGenerateId) {
+            null
+        } else {
+            val keys = primaryKey.fields
+                    .map { "`${it.columnName}`" }
+                    .joinToString(", ")
+            "PRIMARY KEY($keys)"
+        }
+    }
+
+    fun shouldBeDeletedAfter(other: Entity): Boolean {
+        return foreignKeys.any {
+            it.parentTable == other.tableName
+                    && ((!it.deferred && it.onDelete == ForeignKeyAction.NO_ACTION)
+                    || it.onDelete == ForeignKeyAction.RESTRICT)
+        }
+    }
+
+    fun toBundle(): EntityBundle = EntityBundle(
+            tableName,
+            createTableQuery(BundleUtil.TABLE_NAME_PLACEHOLDER),
+            fields.map { it.toBundle() },
+            primaryKey.toBundle(),
+            indices.map { it.toBundle() },
+            foreignKeys.map { it.toBundle() })
+
+    fun isUnique(columns: List<String>): Boolean {
+        return if (primaryKey.columnNames.size == columns.size
+                && primaryKey.columnNames.containsAll(columns)) {
+            true
+        } else {
+            indices.any { index ->
+                index.unique
+                        && index.fields.size == columns.size
+                        && index.columnNames.containsAll(columns)
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Field.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Field.kt
new file mode 100644
index 0000000..b8b1513
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Field.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.ext.isNonNull
+import androidx.room.ext.typeName
+import androidx.room.migration.bundle.FieldBundle
+import androidx.room.parser.Collate
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.solver.types.CursorValueReader
+import androidx.room.solver.types.StatementValueBinder
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+// used in cache matching, must stay as a data class or implement equals
+data class Field(val element: Element, val name: String, val type: TypeMirror,
+                 var affinity: SQLTypeAffinity?,
+                 val collate: Collate? = null,
+                 val columnName: String = name,
+                 /* means that this field does not belong to parent, instead, it belongs to a
+                 * embedded child of the main Pojo*/
+                 val parent: EmbeddedField? = null,
+                 // index might be removed when being merged into an Entity
+                 var indexed: Boolean = false) : HasSchemaIdentity {
+    lateinit var getter: FieldGetter
+    lateinit var setter: FieldSetter
+    // binds the field into a statement
+    var statementBinder: StatementValueBinder? = null
+    // reads this field from a cursor column
+    var cursorValueReader: CursorValueReader? = null
+    val typeName: TypeName by lazy { type.typeName() }
+
+    /** Whether the table column for this field should be NOT NULL */
+    val nonNull = element.isNonNull() && (parent == null || parent.isNonNullRecursively())
+
+    override fun getIdKey(): String {
+        // we don't get the collate information from sqlite so ignoring it here.
+        return "$columnName-${affinity?.name ?: SQLTypeAffinity.TEXT.name}-$nonNull"
+    }
+
+    /**
+     * Used when reporting errors on duplicate names
+     */
+    fun getPath(): String {
+        return if (parent == null) {
+            name
+        } else {
+            "${parent.field.getPath()} > $name"
+        }
+    }
+
+    private val pathWithDotNotation: String by lazy {
+        if (parent == null) {
+            name
+        } else {
+            "${parent.field.pathWithDotNotation}.$name"
+        }
+    }
+
+    /**
+     * List of names that include variations.
+     * e.g. if it is mUser, user is added to the list
+     * or if it is isAdmin, admin is added to the list
+     */
+    val nameWithVariations by lazy {
+        val result = arrayListOf(name)
+        if (name.length > 1) {
+            if (name.startsWith('_')) {
+                result.add(name.substring(1))
+            }
+            if (name.startsWith("m") && name[1].isUpperCase()) {
+                result.add(name.substring(1).decapitalize())
+            }
+
+            if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
+                if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) {
+                    result.add(name.substring(2).decapitalize())
+                }
+                if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) {
+                    result.add(name.substring(3).decapitalize())
+                }
+            }
+        }
+        result
+    }
+
+    val getterNameWithVariations by lazy {
+        nameWithVariations.map { "get${it.capitalize()}" } +
+                if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
+                    nameWithVariations.flatMap {
+                        listOf("is${it.capitalize()}", "has${it.capitalize()}")
+                    }
+                } else {
+                    emptyList()
+                }
+    }
+
+    val setterNameWithVariations by lazy {
+        nameWithVariations.map { "set${it.capitalize()}" }
+    }
+
+    /**
+     * definition to be used in create query
+     */
+    fun databaseDefinition(autoIncrementPKey: Boolean): String {
+        val columnSpec = StringBuilder("")
+        if (autoIncrementPKey) {
+            columnSpec.append(" PRIMARY KEY AUTOINCREMENT")
+        }
+        if (nonNull) {
+            columnSpec.append(" NOT NULL")
+        }
+        if (collate != null) {
+            columnSpec.append(" COLLATE ${collate.name}")
+        }
+        return "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}$columnSpec"
+    }
+
+    fun toBundle(): FieldBundle = FieldBundle(pathWithDotNotation, columnName,
+            affinity?.name ?: SQLTypeAffinity.TEXT.name, nonNull
+    )
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/FieldGetter.kt b/room/compiler/src/main/kotlin/androidx/room/vo/FieldGetter.kt
new file mode 100644
index 0000000..74f450b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/FieldGetter.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.TypeName
+import javax.lang.model.type.TypeMirror
+
+data class FieldGetter(val name: String, val type: TypeMirror, val callType: CallType) {
+    fun writeGet(ownerVar: String, outVar: String, builder: CodeBlock.Builder) {
+        val stmt = when (callType) {
+            CallType.FIELD -> "final $T $L = $L.$L"
+            CallType.METHOD -> "final $T $L = $L.$L()"
+            CallType.CONSTRUCTOR -> null
+        }
+        stmt?.let {
+            builder.addStatement(stmt, TypeName.get(type), outVar, ownerVar, name)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/FieldSetter.kt b/room/compiler/src/main/kotlin/androidx/room/vo/FieldSetter.kt
new file mode 100644
index 0000000..7c0001c
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/FieldSetter.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.ext.L
+import com.squareup.javapoet.CodeBlock
+import javax.lang.model.type.TypeMirror
+
+data class FieldSetter(val name: String, val type: TypeMirror, val callType: CallType) {
+    fun writeSet(ownerVar: String, inVar: String, builder: CodeBlock.Builder) {
+        val stmt = when (callType) {
+            CallType.FIELD -> "$L.$L = $L"
+            CallType.METHOD -> "$L.$L($L)"
+            CallType.CONSTRUCTOR -> null
+        }
+        stmt?.let {
+            builder.addStatement(stmt, ownerVar, name, inVar)
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/FieldWithIndex.kt b/room/compiler/src/main/kotlin/androidx/room/vo/FieldWithIndex.kt
new file mode 100644
index 0000000..4a75db0
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/FieldWithIndex.kt
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.room.vo
+
+/**
+ * A common value object when we need to associate a Field with an Index
+ * variable.
+ * <p>
+ * If we are sure that the field will be there at compile time, we set it to always Exists so that
+ * the generated code does not check for -1 column indices.
+ */
+data class FieldWithIndex(val field: Field, val indexVar: String, val alwaysExists: Boolean) {
+    companion object {
+        fun byOrder(fields: List<Field>): List<FieldWithIndex> {
+            return fields.mapIndexed { index, field ->
+                FieldWithIndex(field = field,
+                        indexVar = "${index + 1}",
+                        alwaysExists = true)
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/ForeignKey.kt b/room/compiler/src/main/kotlin/androidx/room/vo/ForeignKey.kt
new file mode 100644
index 0000000..2481486
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/ForeignKey.kt
@@ -0,0 +1,62 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.migration.bundle.ForeignKeyBundle
+
+/**
+ * Keeps information about a foreign key.
+ */
+data class ForeignKey(val parentTable: String,
+                      val parentColumns: List<String>,
+                      val childFields: List<Field>,
+                      val onDelete: ForeignKeyAction,
+                      val onUpdate: ForeignKeyAction,
+                      val deferred: Boolean) : HasSchemaIdentity {
+    override fun getIdKey(): String {
+        return parentTable +
+                "-${parentColumns.joinToString(",")}" +
+                "-${childFields.joinToString(",") {it.columnName}}" +
+                "-${onDelete.sqlName}" +
+                "-${onUpdate.sqlName}" +
+                "-$deferred"
+    }
+
+    fun databaseDefinition(): String {
+        return "FOREIGN KEY(${joinEscaped(childFields.map { it.columnName })})" +
+                " REFERENCES `$parentTable`(${joinEscaped(parentColumns)})" +
+                " ON UPDATE ${onUpdate.sqlName}" +
+                " ON DELETE ${onDelete.sqlName}" +
+                " ${deferredDeclaration()}"
+    }
+
+    private fun deferredDeclaration(): String {
+        return if (deferred) {
+            "DEFERRABLE INITIALLY DEFERRED"
+        } else {
+            ""
+        }
+    }
+
+    private fun joinEscaped(values: Iterable<String>) = values.joinToString(", ") { "`$it`" }
+
+    fun toBundle(): ForeignKeyBundle = ForeignKeyBundle(
+            parentTable, onDelete.sqlName, onUpdate.sqlName,
+            childFields.map { it.columnName },
+            parentColumns
+    )
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/ForeignKeyAction.kt b/room/compiler/src/main/kotlin/androidx/room/vo/ForeignKeyAction.kt
new file mode 100644
index 0000000..2d16827
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/ForeignKeyAction.kt
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.ForeignKey
+
+/**
+ * Compiler representation of ForeignKey#Action.
+ */
+enum class ForeignKeyAction(val annotationValue: Int, val sqlName: String) {
+    NO_ACTION(ForeignKey.NO_ACTION, "NO ACTION"),
+    RESTRICT(ForeignKey.RESTRICT, "RESTRICT"),
+    SET_NULL(ForeignKey.SET_NULL, "SET NULL"),
+    SET_DEFAULT(ForeignKey.SET_DEFAULT, "SET DEFAULT"),
+    CASCADE(ForeignKey.CASCADE, "CASCADE");
+    companion object {
+        private val mapping by lazy {
+            ForeignKeyAction.values().associateBy { it.annotationValue }
+        }
+        fun fromAnnotationValue(value: Int?) = mapping[value]
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Index.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Index.kt
new file mode 100644
index 0000000..1bc4c67
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Index.kt
@@ -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 androidx.room.vo
+
+import androidx.room.migration.bundle.BundleUtil
+import androidx.room.migration.bundle.IndexBundle
+
+/**
+ * Represents a processed index.
+ */
+data class Index(val name: String, val unique: Boolean, val fields: List<Field>) :
+        HasSchemaIdentity {
+    companion object {
+        // should match the value in TableInfo.Index.DEFAULT_PREFIX
+        const val DEFAULT_PREFIX = "index_"
+    }
+
+    override fun getIdKey(): String {
+        return "$unique-$name-${fields.joinToString(",") { it.columnName }}"
+    }
+
+    fun createQuery(tableName: String): String {
+        val uniqueSQL = if (unique) {
+            "UNIQUE"
+        } else {
+            ""
+        }
+        return """
+            CREATE $uniqueSQL INDEX `$name`
+            ON `$tableName` (${fields.map { it.columnName }.joinToString(", ") { "`$it`" }})
+            """.trimIndent().replace("\n", " ")
+    }
+
+    val columnNames by lazy { fields.map { it.columnName } }
+
+    fun toBundle(): IndexBundle = IndexBundle(name, unique, fields.map { it.columnName },
+            createQuery(BundleUtil.TABLE_NAME_PLACEHOLDER))
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt
new file mode 100644
index 0000000..5bc41d3
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/InsertionMethod.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.OnConflictStrategy
+import androidx.room.ext.typeName
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.TypeMirror
+
+data class InsertionMethod(val element: ExecutableElement, val name: String,
+                           @OnConflictStrategy val onConflict: Int,
+                           val entities: Map<String, Entity>, val returnType: TypeMirror,
+                           val insertionType: Type?,
+                           val parameters: List<ShortcutQueryParameter>) {
+    fun insertMethodTypeFor(param: ShortcutQueryParameter): Type {
+        return if (insertionType == Type.INSERT_VOID || insertionType == null) {
+            Type.INSERT_VOID
+        } else if (!param.isMultiple) {
+            Type.INSERT_SINGLE_ID
+        } else {
+            insertionType
+        }
+    }
+
+    enum class Type(
+            // methodName matches EntityInsertionAdapter methods
+            val methodName: String, val returnTypeName: TypeName) {
+        INSERT_VOID("insert", TypeName.VOID), // return void
+        INSERT_SINGLE_ID("insertAndReturnId", TypeName.LONG), // return long
+        INSERT_ID_ARRAY("insertAndReturnIdsArray",
+                ArrayTypeName.of(TypeName.LONG)), // return long[]
+        INSERT_ID_ARRAY_BOX("insertAndReturnIdsArrayBox",
+                ArrayTypeName.of(TypeName.LONG.box())), // return Long[]
+        INSERT_ID_LIST("insertAndReturnIdsList", // return List<Long>
+                ParameterizedTypeName.get(List::class.typeName(), TypeName.LONG.box()))
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Pojo.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Pojo.kt
new file mode 100644
index 0000000..5debcc2
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Pojo.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.ext.typeName
+import androidx.room.processor.EntityProcessor
+import com.google.auto.common.MoreElements
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+/**
+ * A class is turned into a Pojo if it is used in a query response.
+ */
+open class Pojo(
+        val element: TypeElement,
+        val type: DeclaredType,
+        val fields: List<Field>,
+        val embeddedFields: List<EmbeddedField>,
+        val relations: List<Relation>,
+        val constructor: Constructor? = null) {
+    val typeName: TypeName by lazy { type.typeName() }
+
+    /**
+     * All table names that are somehow accessed by this Pojo.
+     * Might be via Embedded or Relation.
+     */
+    fun accessedTableNames(): List<String> {
+        val entityAnnotation = MoreElements.getAnnotationMirror(element,
+                androidx.room.Entity::class.java).orNull()
+        return if (entityAnnotation != null) {
+            listOf(EntityProcessor.extractTableName(element, entityAnnotation))
+        } else {
+            embeddedFields.flatMap {
+                it.pojo.accessedTableNames()
+            } + relations.map {
+                it.entity.tableName
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/PojoMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/PojoMethod.kt
new file mode 100644
index 0000000..6639025
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/PojoMethod.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 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 androidx.room.vo
+
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.ExecutableType
+
+/**
+ * An executable element processed as member of a class (pojo or entity)
+ */
+class PojoMethod(
+        val element: ExecutableElement,
+        val resolvedType: ExecutableType,
+        val name: String)
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/PrimaryKey.kt b/room/compiler/src/main/kotlin/androidx/room/vo/PrimaryKey.kt
new file mode 100644
index 0000000..95f4a2b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/PrimaryKey.kt
@@ -0,0 +1,43 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.migration.bundle.PrimaryKeyBundle
+import javax.lang.model.element.Element
+
+/**
+ * Represents a PrimaryKey for an Entity.
+ */
+data class PrimaryKey(val declaredIn: Element?, val fields: List<Field>,
+                      val autoGenerateId: Boolean) : HasSchemaIdentity {
+    companion object {
+        val MISSING = PrimaryKey(null, emptyList(), false)
+    }
+
+    val columnNames by lazy { fields.map { it.columnName } }
+
+    fun toHumanReadableString(): String {
+        return "PrimaryKey[" +
+                fields.joinToString(separator = ", ", transform = Field::getPath) + "]"
+    }
+
+    fun toBundle(): PrimaryKeyBundle = PrimaryKeyBundle(
+            autoGenerateId, fields.map { it.columnName })
+
+    override fun getIdKey(): String {
+        return "$autoGenerateId-${fields.map { it.columnName }}"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/QueryMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/QueryMethod.kt
new file mode 100644
index 0000000..9b2c7a1
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/QueryMethod.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.ext.typeName
+import androidx.room.parser.ParsedQuery
+import androidx.room.solver.query.result.QueryResultBinder
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A class that holds information about a QueryMethod.
+ * It is self sufficient and must have all generics etc resolved once created.
+ */
+data class QueryMethod(val element: ExecutableElement, val query: ParsedQuery, val name: String,
+                       val returnType: TypeMirror, val parameters: List<QueryParameter>,
+                       val inTransaction: Boolean,
+                       val queryResultBinder: QueryResultBinder) {
+    val sectionToParamMapping by lazy {
+        query.bindSections.map {
+            if (it.text.trim() == "?") {
+                Pair(it, parameters.firstOrNull())
+            } else if (it.text.startsWith(":")) {
+                val subName = it.text.substring(1)
+                Pair(it, parameters.firstOrNull {
+                    it.sqlName == subName
+                })
+            } else {
+                Pair(it, null)
+            }
+        }
+    }
+
+    val returnsValue by lazy {
+        returnType.typeName() != TypeName.VOID
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/QueryParameter.kt b/room/compiler/src/main/kotlin/androidx/room/vo/QueryParameter.kt
new file mode 100644
index 0000000..3ac5424
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/QueryParameter.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import androidx.room.solver.query.parameter.QueryParameterAdapter
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Holds the parameter for a {@link QueryMethod}.
+ */
+data class QueryParameter(
+        // this is name seen by java
+        val name: String,
+        // this is the name used in the query. Might be different for kotlin queries
+        val sqlName: String,
+        val type: TypeMirror,
+        val queryParamAdapter: QueryParameterAdapter?)
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
new file mode 100644
index 0000000..027bd45
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 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 androidx.room.vo
+
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.ext.typeName
+import androidx.room.solver.query.result.QueryResultBinder
+import com.squareup.javapoet.TypeName
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.TypeMirror
+
+/**
+ * A class that holds information about a method annotated with RawQuery.
+ * It is self sufficient and must have all generics etc resolved once created.
+ */
+data class RawQueryMethod(
+        val element: ExecutableElement,
+        val name: String,
+        val returnType: TypeMirror,
+        val inTransaction: Boolean,
+        val observedTableNames: Set<String>,
+        val runtimeQueryParam: RuntimeQueryParameter?,
+        val queryResultBinder: QueryResultBinder) {
+    val returnsValue by lazy {
+        returnType.typeName() != TypeName.VOID
+    }
+
+    data class RuntimeQueryParameter(
+            val paramName: String,
+            val type: TypeName) {
+        fun isString() = CommonTypeNames.STRING == type
+        fun isSupportQuery() = SupportDbTypeNames.QUERY == type
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Relation.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Relation.kt
new file mode 100644
index 0000000..b97417d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Relation.kt
@@ -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 androidx.room.vo
+
+import androidx.room.ext.typeName
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Value object created from processing a @Relation annotation.
+ */
+class Relation(
+        val entity: Entity,
+        // return type. e..g. String in @Relation List<String>
+        val pojoType: TypeMirror,
+        // field in Pojo that holds these relations (e.g. List<Pet> pets)
+        val field: Field,
+        // the parent field referenced for matching
+        val parentField: Field,
+        // the field referenced for querying. does not need to be in the response but the query
+        // we generate always has it in the response.
+        val entityField: Field,
+        // the projection for the query
+        val projection: List<String>) {
+
+    val pojoTypeName by lazy { pojoType.typeName() }
+
+    fun createLoadAllSql(): String {
+        val resultFields = projection.toSet() + entityField.columnName
+        return createSelect(resultFields)
+    }
+
+    private fun createSelect(resultFields: Set<String>): String {
+        return "SELECT ${resultFields.joinToString(",") {"`$it`"}}" +
+                " FROM `${entity.tableName}`" +
+                " WHERE `${entityField.columnName}` IN (:args)"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
new file mode 100644
index 0000000..8523193
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -0,0 +1,268 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.parser.ParsedQuery
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.parser.SqlParser
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER
+import androidx.room.processor.ProcessorErrors.relationAffinityMismatch
+import androidx.room.solver.CodeGenScope
+import androidx.room.solver.query.result.RowAdapter
+import androidx.room.solver.query.result.SingleColumnRowAdapter
+import androidx.room.verifier.DatabaseVerificaitonErrors
+import androidx.room.writer.QueryWriter
+import androidx.room.writer.RelationCollectorMethodWriter
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import java.util.ArrayList
+import java.util.HashSet
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Internal class that is used to manage fetching 1/N to N relationships.
+ */
+data class RelationCollector(val relation: Relation,
+                             val affinity: SQLTypeAffinity,
+                             val mapTypeName: ParameterizedTypeName,
+                             val keyTypeName: TypeName,
+                             val collectionTypeName: ParameterizedTypeName,
+                             val queryWriter: QueryWriter,
+                             val rowAdapter: RowAdapter,
+                             val loadAllQuery: ParsedQuery) {
+    // set when writing the code generator in writeInitCode
+    lateinit var varName: String
+
+    fun writeInitCode(scope: CodeGenScope) {
+        val tmpVar = scope.getTmpVar(
+                "_collection${relation.field.getPath().stripNonJava().capitalize()}")
+        scope.builder().addStatement("final $T $L = new $T()", mapTypeName, tmpVar, mapTypeName)
+        varName = tmpVar
+    }
+
+    // called after reading each item to extract the key if it exists
+    fun writeReadParentKeyCode(cursorVarName: String, itemVar: String,
+                               fieldsWithIndices: List<FieldWithIndex>, scope: CodeGenScope) {
+        val indexVar = fieldsWithIndices.firstOrNull {
+            it.field === relation.parentField
+        }?.indexVar
+        scope.builder().apply {
+            readKey(
+                    cursorVarName = cursorVarName,
+                    indexVar = indexVar,
+                    scope = scope
+            ) { tmpVar ->
+                val tmpCollectionVar = scope.getTmpVar("_tmpCollection")
+                addStatement("$T $L = $L.get($L)", collectionTypeName, tmpCollectionVar,
+                        varName, tmpVar)
+                beginControlFlow("if($L == null)", tmpCollectionVar).apply {
+                    addStatement("$L = new $T()", tmpCollectionVar, collectionTypeName)
+                    addStatement("$L.put($L, $L)", varName, tmpVar, tmpCollectionVar)
+                }
+                endControlFlow()
+                // set it on the item
+                relation.field.setter.writeSet(itemVar, tmpCollectionVar, this)
+            }
+        }
+    }
+
+    fun writeCollectionCode(scope: CodeGenScope) {
+        val method = scope.writer
+                .getOrCreateMethod(RelationCollectorMethodWriter(this))
+        scope.builder().apply {
+            addStatement("$N($L)", method, varName)
+        }
+    }
+
+    fun readKey(cursorVarName: String, indexVar: String?, scope: CodeGenScope,
+                postRead: CodeBlock.Builder.(String) -> Unit) {
+        val cursorGetter = when (affinity) {
+            SQLTypeAffinity.INTEGER -> "getLong"
+            SQLTypeAffinity.REAL -> "getDouble"
+            SQLTypeAffinity.TEXT -> "getString"
+            SQLTypeAffinity.BLOB -> "getBlob"
+            else -> {
+                "getString"
+            }
+        }
+        scope.builder().apply {
+            beginControlFlow("if (!$L.isNull($L))", cursorVarName, indexVar).apply {
+                val tmpVar = scope.getTmpVar("_tmpKey")
+                addStatement("final $T $L = $L.$L($L)", keyTypeName,
+                        tmpVar, cursorVarName, cursorGetter, indexVar)
+                this.postRead(tmpVar)
+            }
+            endControlFlow()
+        }
+    }
+
+    companion object {
+        fun createCollectors(
+                baseContext: Context,
+                relations: List<Relation>
+        ): List<RelationCollector> {
+            return relations.map { relation ->
+                // decide on the affinity
+                val context = baseContext.fork(relation.field.element)
+                val parentAffinity = relation.parentField.cursorValueReader?.affinity()
+                val childAffinity = relation.entityField.cursorValueReader?.affinity()
+                val affinity = if (parentAffinity != null && parentAffinity == childAffinity) {
+                    parentAffinity
+                } else {
+                    context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
+                            relationAffinityMismatch(
+                                    parentColumn = relation.parentField.columnName,
+                                    childColumn = relation.entityField.columnName,
+                                    parentAffinity = parentAffinity,
+                                    childAffinity = childAffinity))
+                    SQLTypeAffinity.TEXT
+                }
+                val keyType = keyTypeFor(context, affinity)
+                val collectionTypeName = if (relation.field.typeName is ParameterizedTypeName) {
+                    val paramType = relation.field.typeName as ParameterizedTypeName
+                    if (paramType.rawType == CommonTypeNames.LIST) {
+                        ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+                                relation.pojoTypeName)
+                    } else if (paramType.rawType == CommonTypeNames.SET) {
+                        ParameterizedTypeName.get(ClassName.get(HashSet::class.java),
+                                relation.pojoTypeName)
+                    } else {
+                        ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+                                relation.pojoTypeName)
+                    }
+                } else {
+                    ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
+                            relation.pojoTypeName)
+                }
+
+                val canUseArrayMap = context.processingEnv.elementUtils
+                        .getTypeElement(AndroidTypeNames.ARRAY_MAP.toString()) != null
+                val mapClass = if (canUseArrayMap) {
+                    AndroidTypeNames.ARRAY_MAP
+                } else {
+                    ClassName.get(java.util.HashMap::class.java)
+                }
+                val tmpMapType = ParameterizedTypeName.get(mapClass, keyType, collectionTypeName)
+                val keyTypeMirror = keyTypeMirrorFor(context, affinity)
+                val set = context.processingEnv.elementUtils.getTypeElement("java.util.Set")
+                val keySet = context.processingEnv.typeUtils.getDeclaredType(set, keyTypeMirror)
+                val loadAllQuery = relation.createLoadAllSql()
+                val parsedQuery = SqlParser.parse(loadAllQuery)
+                context.checker.check(parsedQuery.errors.isEmpty(), relation.field.element,
+                        parsedQuery.errors.joinToString("\n"))
+                if (parsedQuery.errors.isEmpty()) {
+                    val resultInfo = context.databaseVerifier?.analyze(loadAllQuery)
+                    parsedQuery.resultInfo = resultInfo
+                    if (resultInfo?.error != null) {
+                        context.logger.e(relation.field.element,
+                                DatabaseVerificaitonErrors.cannotVerifyQuery(resultInfo.error))
+                    }
+                }
+                val resultInfo = parsedQuery.resultInfo
+
+                val queryParam = QueryParameter(
+                        name = RelationCollectorMethodWriter.KEY_SET_VARIABLE,
+                        sqlName = RelationCollectorMethodWriter.KEY_SET_VARIABLE,
+                        type = keySet,
+                        queryParamAdapter =
+                                context.typeAdapterStore.findQueryParameterAdapter(keySet))
+                val queryWriter = QueryWriter(
+                        parameters = listOf(queryParam),
+                        sectionToParamMapping = listOf(Pair(parsedQuery.bindSections.first(),
+                                queryParam)),
+                        query = parsedQuery
+                )
+
+                // row adapter that matches full response
+                fun getDefaultRowAdapter(): RowAdapter? {
+                    return context.typeAdapterStore.findRowAdapter(relation.pojoType, parsedQuery)
+                }
+                val rowAdapter = if (relation.projection.size == 1 && resultInfo != null &&
+                        (resultInfo.columns.size == 1 || resultInfo.columns.size == 2)) {
+                    // check for a column adapter first
+                    val cursorReader = context.typeAdapterStore.findCursorValueReader(
+                            relation.pojoType, resultInfo.columns.first().type)
+                    if (cursorReader == null) {
+                        getDefaultRowAdapter()
+                    } else {
+                        context.logger.d("Choosing cursor adapter for the return value since" +
+                                " the query returns only 1 or 2 columns and there is a cursor" +
+                                " adapter for the return type.")
+                        SingleColumnRowAdapter(cursorReader)
+                    }
+                } else {
+                    getDefaultRowAdapter()
+                }
+
+                if (rowAdapter == null) {
+                    context.logger.e(relation.field.element, CANNOT_FIND_QUERY_RESULT_ADAPTER)
+                    null
+                } else {
+                    RelationCollector(
+                            relation = relation,
+                            affinity = affinity,
+                            mapTypeName = tmpMapType,
+                            keyTypeName = keyType,
+                            collectionTypeName = collectionTypeName,
+                            queryWriter = queryWriter,
+                            rowAdapter = rowAdapter,
+                            loadAllQuery = parsedQuery
+                    )
+                }
+            }.filterNotNull()
+        }
+
+        private fun keyTypeMirrorFor(context: Context, affinity: SQLTypeAffinity): TypeMirror {
+            val types = context.processingEnv.typeUtils
+            val elements = context.processingEnv.elementUtils
+            return when (affinity) {
+                SQLTypeAffinity.INTEGER -> elements.getTypeElement("java.lang.Long").asType()
+                SQLTypeAffinity.REAL -> elements.getTypeElement("java.lang.Double").asType()
+                SQLTypeAffinity.TEXT -> context.COMMON_TYPES.STRING
+                SQLTypeAffinity.BLOB -> types.getArrayType(types.getPrimitiveType(TypeKind.BYTE))
+                else -> {
+                    context.COMMON_TYPES.STRING
+                }
+            }
+        }
+
+        private fun keyTypeFor(context: Context, affinity: SQLTypeAffinity): TypeName {
+            return when (affinity) {
+                SQLTypeAffinity.INTEGER -> TypeName.LONG.box()
+                SQLTypeAffinity.REAL -> TypeName.DOUBLE.box()
+                SQLTypeAffinity.TEXT -> TypeName.get(String::class.java)
+                SQLTypeAffinity.BLOB -> ArrayTypeName.of(TypeName.BYTE)
+                else -> {
+                    // no affinity select from type
+                    context.COMMON_TYPES.STRING.typeName()
+                }
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/SchemaIdentityKey.kt b/room/compiler/src/main/kotlin/androidx/room/vo/SchemaIdentityKey.kt
new file mode 100644
index 0000000..9a241fa
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/SchemaIdentityKey.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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 androidx.room.vo
+
+import org.apache.commons.codec.digest.DigestUtils
+import java.util.Locale
+
+interface HasSchemaIdentity {
+    fun getIdKey(): String
+}
+
+/**
+ * A class that can be converted into a unique identifier for an object
+ */
+class SchemaIdentityKey {
+    companion object {
+        private val SEPARATOR = "?:?"
+        private val ENGLISH_SORT = Comparator<String> { o1, o2 ->
+            o1.toLowerCase(Locale.ENGLISH).compareTo(o2.toLowerCase(Locale.ENGLISH))
+        }
+    }
+
+    private val sb = StringBuilder()
+    fun append(identity: HasSchemaIdentity) {
+        append(identity.getIdKey())
+    }
+
+    fun appendSorted(identities: List<HasSchemaIdentity>) {
+        identities.map { it.getIdKey() }.sortedWith(ENGLISH_SORT).forEach {
+            append(it)
+        }
+    }
+
+    fun hash() = DigestUtils.md5Hex(sb.toString())
+    fun append(identity: String) {
+        sb.append(identity).append(SEPARATOR)
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt
new file mode 100644
index 0000000..dcaa78d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/ShortcutMethod.kt
@@ -0,0 +1,26 @@
+/*
+ * 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 androidx.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * Base class for shortcut methods in @DAO.
+ */
+abstract class ShortcutMethod(val element: ExecutableElement, val name: String,
+                              val entities: Map<String, Entity>, val returnCount: Boolean,
+                              val parameters: List<ShortcutQueryParameter>)
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt b/room/compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
new file mode 100644
index 0000000..d33f3e0
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/ShortcutQueryParameter.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.vo
+
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Parameters used in DAO methods that are annotated with Insert, Delete, Update.
+ */
+data class ShortcutQueryParameter(val name: String, val type: TypeMirror,
+                                  val entityType: TypeMirror?, val isMultiple: Boolean) {
+    /**
+     * Method name in entity insertion or update adapter.
+     */
+    fun handleMethodName(): String {
+        return if (isMultiple) {
+            "handleMultiple"
+        } else {
+            "handle"
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/TransactionMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/TransactionMethod.kt
new file mode 100644
index 0000000..9457f43
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/TransactionMethod.kt
@@ -0,0 +1,25 @@
+/*
+ * 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 androidx.room.vo
+
+import javax.lang.model.element.ExecutableElement
+
+class TransactionMethod(val element: ExecutableElement, val name: String, val callType: CallType) {
+    enum class CallType {
+        CONCRETE, DEFAULT_JAVA8, DEFAULT_KOTLIN
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/UpdateMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/UpdateMethod.kt
new file mode 100644
index 0000000..08b11f4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/UpdateMethod.kt
@@ -0,0 +1,26 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.OnConflictStrategy
+import javax.lang.model.element.ExecutableElement
+
+class UpdateMethod(element: ExecutableElement, name: String,
+                   entities: Map<String, Entity>, returnCount: Boolean,
+                   parameters: List<ShortcutQueryParameter>,
+                   @OnConflictStrategy val onConflictStrategy: Int) : ShortcutMethod(
+        element, name, entities, returnCount, parameters)
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Warning.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Warning.kt
new file mode 100644
index 0000000..561f96d
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Warning.kt
@@ -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 androidx.room.vo
+
+/**
+ * Internal representation of supported warnings
+ */
+enum class Warning(val publicKey: String) {
+    ALL("ALL"),
+    CURSOR_MISMATCH("ROOM_CURSOR_MISMATCH"),
+    MISSING_JAVA_TMP_DIR("ROOM_MISSING_JAVA_TMP_DIR"),
+    CANNOT_CREATE_VERIFICATION_DATABASE("ROOM_CANNOT_CREATE_VERIFICATION_DATABASE"),
+    PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED("ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED"),
+    INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED("ROOM_EMBEDDED_INDEX_IS_DROPPED"),
+    INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED("ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED"),
+    INDEX_FROM_PARENT_IS_DROPPED("ROOM_PARENT_INDEX_IS_DROPPED"),
+    INDEX_FROM_PARENT_FIELD_IS_DROPPED("ROOM_PARENT_FIELD_INDEX_IS_DROPPED"),
+    RELATION_TYPE_MISMATCH("ROOM_RELATION_TYPE_MISMATCH"),
+    MISSING_SCHEMA_LOCATION("ROOM_MISSING_SCHEMA_LOCATION"),
+    MISSING_INDEX_ON_FOREIGN_KEY_CHILD("ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX"),
+    RELATION_QUERY_WITHOUT_TRANSACTION("ROOM_RELATION_QUERY_WITHOUT_TRANSACTION"),
+    DEFAULT_CONSTRUCTOR("ROOM_DEFAULT_CONSTRUCTOR");
+
+    companion object {
+        val PUBLIC_KEY_MAP = Warning.values().associateBy { it.publicKey }
+        fun fromPublicKey(publicKey: String): Warning? {
+            return PUBLIC_KEY_MAP[publicKey.toUpperCase()]
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/ClassWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/ClassWriter.kt
new file mode 100644
index 0000000..a7ebef2
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/ClassWriter.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.RoomProcessor
+import androidx.room.ext.S
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope.Companion.CLASS_PROPERTY_PREFIX
+import com.squareup.javapoet.AnnotationSpec
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.annotation.processing.ProcessingEnvironment
+
+/**
+ * Base class for all writers that can produce a class.
+ */
+abstract class ClassWriter(private val className: ClassName) {
+    private val sharedFieldSpecs = mutableMapOf<String, FieldSpec>()
+    private val sharedMethodSpecs = mutableMapOf<String, MethodSpec>()
+    private val sharedFieldNames = mutableSetOf<String>()
+    private val sharedMethodNames = mutableSetOf<String>()
+
+    abstract fun createTypeSpecBuilder(): TypeSpec.Builder
+
+    fun write(processingEnv: ProcessingEnvironment) {
+        val builder = createTypeSpecBuilder()
+        sharedFieldSpecs.values.forEach { builder.addField(it) }
+        sharedMethodSpecs.values.forEach { builder.addMethod(it) }
+        addGeneratedAnnotationIfAvailable(builder, processingEnv)
+        addSuppressUnchecked(builder)
+        JavaFile.builder(className.packageName(), builder.build())
+                .build()
+                .writeTo(processingEnv.filer)
+    }
+
+    private fun addSuppressUnchecked(builder: TypeSpec.Builder) {
+        val suppressSpec = AnnotationSpec.builder(SuppressWarnings::class.typeName()).addMember(
+                "value",
+                S,
+                "unchecked"
+        ).build()
+        builder.addAnnotation(suppressSpec)
+    }
+
+    private fun addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder: TypeSpec.Builder,
+                                                  processingEnv: ProcessingEnvironment) {
+        val generatedAnnotationAvailable = processingEnv
+                .elementUtils
+                .getTypeElement(GENERATED_PACKAGE + "." + GENERATED_NAME) != null
+        if (generatedAnnotationAvailable) {
+            val className = ClassName.get(GENERATED_PACKAGE, GENERATED_NAME)
+            val generatedAnnotationSpec =
+                    AnnotationSpec.builder(className).addMember(
+                            "value",
+                            S,
+                            RoomProcessor::class.java.canonicalName).build()
+            adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
+        }
+    }
+
+    private fun makeUnique(set: MutableSet<String>, value: String): String {
+        if (!value.startsWith(CLASS_PROPERTY_PREFIX)) {
+            return makeUnique(set, "$CLASS_PROPERTY_PREFIX$value")
+        }
+        if (set.add(value)) {
+            return value
+        }
+        var index = 1
+        while (true) {
+            if (set.add("${value}_$index")) {
+                return "${value}_$index"
+            }
+            index++
+        }
+    }
+
+    fun getOrCreateField(sharedField: SharedFieldSpec): FieldSpec {
+        return sharedFieldSpecs.getOrPut(sharedField.getUniqueKey(), {
+            sharedField.build(this, makeUnique(sharedFieldNames, sharedField.baseName))
+        })
+    }
+
+    fun getOrCreateMethod(sharedMethod: SharedMethodSpec): MethodSpec {
+        return sharedMethodSpecs.getOrPut(sharedMethod.getUniqueKey(), {
+            sharedMethod.build(this, makeUnique(sharedMethodNames, sharedMethod.baseName))
+        })
+    }
+
+    abstract class SharedFieldSpec(val baseName: String, val type: TypeName) {
+
+        abstract fun getUniqueKey(): String
+
+        abstract fun prepare(writer: ClassWriter, builder: FieldSpec.Builder)
+
+        fun build(classWriter: ClassWriter, name: String): FieldSpec {
+            val builder = FieldSpec.builder(type, name)
+            prepare(classWriter, builder)
+            return builder.build()
+        }
+    }
+
+    abstract class SharedMethodSpec(val baseName: String) {
+
+        abstract fun getUniqueKey(): String
+        abstract fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder)
+
+        fun build(writer: ClassWriter, name: String): MethodSpec {
+            val builder = MethodSpec.methodBuilder(name)
+            prepare(name, writer, builder)
+            return builder.build()
+        }
+    }
+
+    companion object {
+        private const val GENERATED_PACKAGE = "javax.annotation"
+        private const val GENERATED_NAME = "Generated"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
new file mode 100644
index 0000000..5b6fd10
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.parser.QueryType
+import androidx.room.processor.OnConflictProcessor
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.Dao
+import androidx.room.vo.Entity
+import androidx.room.vo.InsertionMethod
+import androidx.room.vo.QueryMethod
+import androidx.room.vo.RawQueryMethod
+import androidx.room.vo.ShortcutMethod
+import androidx.room.vo.TransactionMethod
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import org.jetbrains.kotlin.load.java.JvmAbi
+import stripNonJava
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier.FINAL
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind
+
+/**
+ * Creates the implementation for a class annotated with Dao.
+ */
+class DaoWriter(val dao: Dao, val processingEnv: ProcessingEnvironment)
+    : ClassWriter(dao.typeName) {
+    private val declaredDao = MoreTypes.asDeclared(dao.element.asType())
+
+    companion object {
+        // TODO nothing prevents this from conflicting, we should fix.
+        val dbField: FieldSpec = FieldSpec
+                .builder(RoomTypeNames.ROOM_DB, "__db", PRIVATE, FINAL)
+                .build()
+
+        private fun typeNameToFieldName(typeName: TypeName?): String {
+            if (typeName is ClassName) {
+                return typeName.simpleName()
+            } else {
+                return typeName.toString().replace('.', '_').stripNonJava()
+            }
+        }
+    }
+
+    override fun createTypeSpecBuilder(): TypeSpec.Builder {
+        val builder = TypeSpec.classBuilder(dao.implTypeName)
+        /**
+         * if delete / update query method wants to return modified rows, we need prepared query.
+         * in that case, if args are dynamic, we cannot re-use the query, if not, we should re-use
+         * it. this requires more work but creates good performance.
+         */
+        val groupedDeleteUpdate = dao.queryMethods
+                .filter { it.query.type == QueryType.DELETE || it.query.type == QueryType.UPDATE }
+                .groupBy { it.parameters.any { it.queryParamAdapter?.isMultiple ?: true } }
+        // delete queries that can be prepared ahead of time
+        val preparedDeleteOrUpdateQueries = groupedDeleteUpdate[false] ?: emptyList()
+        // delete queries that must be rebuild every single time
+        val oneOffDeleteOrUpdateQueries = groupedDeleteUpdate[true] ?: emptyList()
+        val shortcutMethods = createInsertionMethods() +
+                createDeletionMethods() + createUpdateMethods() + createTransactionMethods() +
+                createPreparedDeleteOrUpdateQueries(preparedDeleteOrUpdateQueries)
+
+        builder.apply {
+            addModifiers(PUBLIC)
+            if (dao.element.kind == ElementKind.INTERFACE) {
+                addSuperinterface(dao.typeName)
+            } else {
+                superclass(dao.typeName)
+            }
+            addField(dbField)
+            val dbParam = ParameterSpec
+                    .builder(dao.constructorParamType ?: dbField.type, dbField.name).build()
+
+            addMethod(createConstructor(dbParam, shortcutMethods, dao.constructorParamType != null))
+
+            shortcutMethods.forEach {
+                addMethod(it.methodImpl)
+            }
+
+            dao.queryMethods.filter { it.query.type == QueryType.SELECT }.forEach { method ->
+                addMethod(createSelectMethod(method))
+            }
+            oneOffDeleteOrUpdateQueries.forEach {
+                addMethod(createDeleteOrUpdateQueryMethod(it))
+            }
+            dao.rawQueryMethods.forEach {
+                addMethod(createRawQueryMethod(it))
+            }
+        }
+        return builder
+    }
+
+    private fun createPreparedDeleteOrUpdateQueries(
+            preparedDeleteQueries: List<QueryMethod>): List<PreparedStmtQuery> {
+        return preparedDeleteQueries.map { method ->
+            val fieldSpec = getOrCreateField(PreparedStatementField(method))
+            val queryWriter = QueryWriter(method)
+            val fieldImpl = PreparedStatementWriter(queryWriter)
+                    .createAnonymous(this@DaoWriter, dbField)
+            val methodBody = createPreparedDeleteQueryMethodBody(method, fieldSpec, queryWriter)
+            PreparedStmtQuery(mapOf(PreparedStmtQuery.NO_PARAM_FIELD
+                    to (fieldSpec to fieldImpl)), methodBody)
+        }
+    }
+
+    private fun createPreparedDeleteQueryMethodBody(
+            method: QueryMethod,
+            preparedStmtField: FieldSpec,
+            queryWriter: QueryWriter
+    ): MethodSpec {
+        val scope = CodeGenScope(this)
+        val methodBuilder = overrideWithoutAnnotations(method.element, declaredDao).apply {
+            val stmtName = scope.getTmpVar("_stmt")
+            addStatement("final $T $L = $N.acquire()",
+                    SupportDbTypeNames.SQLITE_STMT, stmtName, preparedStmtField)
+            addStatement("$N.beginTransaction()", dbField)
+            beginControlFlow("try").apply {
+                val bindScope = scope.fork()
+                queryWriter.bindArgs(stmtName, emptyList(), bindScope)
+                addCode(bindScope.builder().build())
+                if (method.returnsValue) {
+                    val resultVar = scope.getTmpVar("_result")
+                    addStatement("final $L $L = $L.executeUpdateDelete()",
+                            method.returnType.typeName(), resultVar, stmtName)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                    addStatement("return $L", resultVar)
+                } else {
+                    addStatement("$L.executeUpdateDelete()", stmtName)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+                addStatement("$N.release($L)", preparedStmtField, stmtName)
+            }
+            endControlFlow()
+        }
+        return methodBuilder.build()
+    }
+
+    private fun createTransactionMethods(): List<PreparedStmtQuery> {
+        return dao.transactionMethods.map {
+            PreparedStmtQuery(emptyMap(), createTransactionMethodBody(it))
+        }
+    }
+
+    private fun createTransactionMethodBody(method: TransactionMethod): MethodSpec {
+        val scope = CodeGenScope(this)
+        val methodBuilder = overrideWithoutAnnotations(method.element, declaredDao).apply {
+            addStatement("$N.beginTransaction()", dbField)
+            beginControlFlow("try").apply {
+                val returnsValue = method.element.returnType.kind != TypeKind.VOID
+                val resultVar = if (returnsValue) {
+                    scope.getTmpVar("_result")
+                } else {
+                    null
+                }
+                addDelegateToSuperStatement(method.element, method.callType, resultVar)
+                addStatement("$N.setTransactionSuccessful()", dbField)
+                if (returnsValue) {
+                    addStatement("return $N", resultVar)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+        }
+        return methodBuilder.build()
+    }
+
+    private fun MethodSpec.Builder.addDelegateToSuperStatement(
+            element: ExecutableElement,
+            callType: TransactionMethod.CallType,
+            result: String?) {
+        val params: MutableList<Any> = mutableListOf()
+        val format = buildString {
+            if (result != null) {
+                append("$T $L = ")
+                params.add(element.returnType)
+                params.add(result)
+            }
+            when (callType) {
+                TransactionMethod.CallType.CONCRETE -> {
+                    append("super.$N(")
+                    params.add(element.simpleName)
+                }
+                TransactionMethod.CallType.DEFAULT_JAVA8 -> {
+                    append("$N.super.$N(")
+                    params.add(element.enclosingElement.simpleName)
+                    params.add(element.simpleName)
+                }
+                TransactionMethod.CallType.DEFAULT_KOTLIN -> {
+                    append("$N.$N.$N(this, ")
+                    params.add(element.enclosingElement.simpleName)
+                    params.add(JvmAbi.DEFAULT_IMPLS_CLASS_NAME)
+                    params.add(element.simpleName)
+                }
+            }
+            var first = true
+            element.parameters.forEach {
+                if (first) {
+                    first = false
+                } else {
+                    append(", ")
+                }
+                append(L)
+                params.add(it.simpleName)
+            }
+            append(")")
+        }
+        addStatement(format, *params.toTypedArray())
+    }
+
+    private fun createConstructor(
+            dbParam: ParameterSpec,
+            shortcutMethods: List<PreparedStmtQuery>,
+            callSuper: Boolean): MethodSpec {
+        return MethodSpec.constructorBuilder().apply {
+            addParameter(dbParam)
+            addModifiers(PUBLIC)
+            if (callSuper) {
+                addStatement("super($N)", dbParam)
+            }
+            addStatement("this.$N = $N", dbField, dbParam)
+            shortcutMethods.filterNot {
+                it.fields.isEmpty()
+            }.map {
+                it.fields.values
+            }.flatten().groupBy {
+                it.first.name
+            }.map {
+                it.value.first()
+            }.forEach {
+                addStatement("this.$N = $L", it.first, it.second)
+            }
+        }.build()
+    }
+
+    private fun createSelectMethod(method: QueryMethod): MethodSpec {
+        return overrideWithoutAnnotations(method.element, declaredDao).apply {
+            addCode(createQueryMethodBody(method))
+        }.build()
+    }
+
+    private fun createRawQueryMethod(method: RawQueryMethod): MethodSpec {
+        return overrideWithoutAnnotations(method.element, declaredDao).apply {
+            val scope = CodeGenScope(this@DaoWriter)
+            val roomSQLiteQueryVar: String
+            val queryParam = method.runtimeQueryParam
+            val shouldReleaseQuery: Boolean
+
+            when {
+                queryParam?.isString() == true -> {
+                    roomSQLiteQueryVar = scope.getTmpVar("_statement")
+                    shouldReleaseQuery = true
+                    addStatement("$T $L = $T.acquire($L, 0)",
+                            RoomTypeNames.ROOM_SQL_QUERY,
+                            roomSQLiteQueryVar,
+                            RoomTypeNames.ROOM_SQL_QUERY,
+                            queryParam.paramName)
+                }
+                queryParam?.isSupportQuery() == true -> {
+                    shouldReleaseQuery = false
+                    roomSQLiteQueryVar = scope.getTmpVar("_internalQuery")
+                    // move it to a final variable so that the generated code can use it inside
+                    // callback blocks in java 7
+                    addStatement("final $T $L = $N",
+                            queryParam.type,
+                            roomSQLiteQueryVar,
+                            queryParam.paramName)
+                }
+                else -> {
+                    // try to generate compiling code. we would've already reported this error
+                    roomSQLiteQueryVar = scope.getTmpVar("_statement")
+                    shouldReleaseQuery = false
+                    addStatement("$T $L = $T.acquire($L, 0)",
+                            RoomTypeNames.ROOM_SQL_QUERY,
+                            roomSQLiteQueryVar,
+                            RoomTypeNames.ROOM_SQL_QUERY,
+                            "missing query parameter")
+                }
+            }
+            if (method.returnsValue) {
+                // don't generate code because it will create 1 more error. The original error is
+                // already reported by the processor.
+                method.queryResultBinder.convertAndReturn(
+                        roomSQLiteQueryVar = roomSQLiteQueryVar,
+                        canReleaseQuery = shouldReleaseQuery,
+                        dbField = dbField,
+                        inTransaction = method.inTransaction,
+                        scope = scope)
+            }
+            addCode(scope.builder().build())
+        }.build()
+    }
+
+    private fun createDeleteOrUpdateQueryMethod(method: QueryMethod): MethodSpec {
+        return overrideWithoutAnnotations(method.element, declaredDao).apply {
+            addCode(createDeleteOrUpdateQueryMethodBody(method))
+        }.build()
+    }
+
+    /**
+     * Groups all insertion methods based on the insert statement they will use then creates all
+     * field specs, EntityInsertionAdapterWriter and actual insert methods.
+     */
+    private fun createInsertionMethods(): List<PreparedStmtQuery> {
+        return dao.insertionMethods
+                .map { insertionMethod ->
+                    val onConflict = OnConflictProcessor.onConflictText(insertionMethod.onConflict)
+                    val entities = insertionMethod.entities
+
+                    val fields = entities.mapValues {
+                        val spec = getOrCreateField(InsertionMethodField(it.value, onConflict))
+                        val impl = EntityInsertionAdapterWriter(it.value, onConflict)
+                                .createAnonymous(this@DaoWriter, dbField.name)
+                        spec to impl
+                    }
+                    val methodImpl = overrideWithoutAnnotations(insertionMethod.element,
+                            declaredDao).apply {
+                        addCode(createInsertionMethodBody(insertionMethod, fields))
+                    }.build()
+                    PreparedStmtQuery(fields, methodImpl)
+                }.filterNotNull()
+    }
+
+    private fun createInsertionMethodBody(
+            method: InsertionMethod,
+            insertionAdapters: Map<String, Pair<FieldSpec, TypeSpec>>
+    ): CodeBlock {
+        val insertionType = method.insertionType
+        if (insertionAdapters.isEmpty() || insertionType == null) {
+            return CodeBlock.builder().build()
+        }
+        val scope = CodeGenScope(this)
+
+        return scope.builder().apply {
+            // TODO assert thread
+            // TODO collect results
+            addStatement("$N.beginTransaction()", dbField)
+            val needsReturnType = insertionType != InsertionMethod.Type.INSERT_VOID
+            val resultVar = if (needsReturnType) {
+                scope.getTmpVar("_result")
+            } else {
+                null
+            }
+
+            beginControlFlow("try").apply {
+                method.parameters.forEach { param ->
+                    val insertionAdapter = insertionAdapters[param.name]?.first
+                    if (needsReturnType) {
+                        // if it has more than 1 parameter, we would've already printed the error
+                        // so we don't care about re-declaring the variable here
+                        addStatement("$T $L = $N.$L($L)",
+                                insertionType.returnTypeName, resultVar,
+                                insertionAdapter, insertionType.methodName,
+                                param.name)
+                    } else {
+                        addStatement("$N.$L($L)", insertionAdapter, insertionType.methodName,
+                                param.name)
+                    }
+                }
+                addStatement("$N.setTransactionSuccessful()", dbField)
+                if (needsReturnType) {
+                    addStatement("return $L", resultVar)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+        }.build()
+    }
+
+    /**
+     * Creates EntityUpdateAdapter for each deletion method.
+     */
+    private fun createDeletionMethods(): List<PreparedStmtQuery> {
+        return createShortcutMethods(dao.deletionMethods, "deletion", { _, entity ->
+            EntityDeletionAdapterWriter(entity)
+                    .createAnonymous(this@DaoWriter, dbField.name)
+        })
+    }
+
+    /**
+     * Creates EntityUpdateAdapter for each @Update method.
+     */
+    private fun createUpdateMethods(): List<PreparedStmtQuery> {
+        return createShortcutMethods(dao.updateMethods, "update", { update, entity ->
+            val onConflict = OnConflictProcessor.onConflictText(update.onConflictStrategy)
+            EntityUpdateAdapterWriter(entity, onConflict)
+                    .createAnonymous(this@DaoWriter, dbField.name)
+        })
+    }
+
+    private fun <T : ShortcutMethod> createShortcutMethods(
+            methods: List<T>, methodPrefix: String,
+            implCallback: (T, Entity) -> TypeSpec
+    ): List<PreparedStmtQuery> {
+        return methods.map { method ->
+            val entities = method.entities
+
+            if (entities.isEmpty()) {
+                null
+            } else {
+                val fields = entities.mapValues {
+                    val spec = getOrCreateField(DeleteOrUpdateAdapterField(it.value, methodPrefix))
+                    val impl = implCallback(method, it.value)
+                    spec to impl
+                }
+                val methodSpec = overrideWithoutAnnotations(method.element, declaredDao).apply {
+                    addCode(createDeleteOrUpdateMethodBody(method, fields))
+                }.build()
+                PreparedStmtQuery(fields, methodSpec)
+            }
+        }.filterNotNull()
+    }
+
+    private fun createDeleteOrUpdateMethodBody(
+            method: ShortcutMethod,
+            adapters: Map<String, Pair<FieldSpec, TypeSpec>>
+    ): CodeBlock {
+        if (adapters.isEmpty()) {
+            return CodeBlock.builder().build()
+        }
+        val scope = CodeGenScope(this)
+        val resultVar = if (method.returnCount) {
+            scope.getTmpVar("_total")
+        } else {
+            null
+        }
+        return scope.builder().apply {
+            if (resultVar != null) {
+                addStatement("$T $L = 0", TypeName.INT, resultVar)
+            }
+            addStatement("$N.beginTransaction()", dbField)
+            beginControlFlow("try").apply {
+                method.parameters.forEach { param ->
+                    val adapter = adapters[param.name]?.first
+                    addStatement("$L$N.$L($L)",
+                            if (resultVar == null) "" else "$resultVar +=",
+                            adapter, param.handleMethodName(), param.name)
+                }
+                addStatement("$N.setTransactionSuccessful()", dbField)
+                if (resultVar != null) {
+                    addStatement("return $L", resultVar)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+        }.build()
+    }
+
+    /**
+     * @Query with delete action
+     */
+    private fun createDeleteOrUpdateQueryMethodBody(method: QueryMethod): CodeBlock {
+        val queryWriter = QueryWriter(method)
+        val scope = CodeGenScope(this)
+        val sqlVar = scope.getTmpVar("_sql")
+        val stmtVar = scope.getTmpVar("_stmt")
+        val listSizeArgs = queryWriter.prepareQuery(sqlVar, scope)
+        scope.builder().apply {
+            addStatement("$T $L = $N.compileStatement($L)",
+                    SupportDbTypeNames.SQLITE_STMT, stmtVar, dbField, sqlVar)
+            queryWriter.bindArgs(stmtVar, listSizeArgs, scope)
+            addStatement("$N.beginTransaction()", dbField)
+            beginControlFlow("try").apply {
+                if (method.returnsValue) {
+                    val resultVar = scope.getTmpVar("_result")
+                    addStatement("final $L $L = $L.executeUpdateDelete()",
+                            method.returnType.typeName(), resultVar, stmtVar)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                    addStatement("return $L", resultVar)
+                } else {
+                    addStatement("$L.executeUpdateDelete()", stmtVar)
+                    addStatement("$N.setTransactionSuccessful()", dbField)
+                }
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$N.endTransaction()", dbField)
+            }
+            endControlFlow()
+        }
+        return scope.builder().build()
+    }
+
+    private fun createQueryMethodBody(method: QueryMethod): CodeBlock {
+        val queryWriter = QueryWriter(method)
+        val scope = CodeGenScope(this)
+        val sqlVar = scope.getTmpVar("_sql")
+        val roomSQLiteQueryVar = scope.getTmpVar("_statement")
+        queryWriter.prepareReadAndBind(sqlVar, roomSQLiteQueryVar, scope)
+        method.queryResultBinder.convertAndReturn(
+                roomSQLiteQueryVar = roomSQLiteQueryVar,
+                canReleaseQuery = true,
+                dbField = dbField,
+                inTransaction = method.inTransaction,
+                scope = scope)
+        return scope.builder().build()
+    }
+
+    private fun overrideWithoutAnnotations(
+            elm: ExecutableElement,
+            owner: DeclaredType): MethodSpec.Builder {
+        val baseSpec = MethodSpec.overriding(elm, owner, processingEnv.typeUtils).build()
+        return MethodSpec.methodBuilder(baseSpec.name).apply {
+            addAnnotation(Override::class.java)
+            addModifiers(baseSpec.modifiers)
+            addParameters(baseSpec.parameters)
+            varargs(baseSpec.varargs)
+            returns(baseSpec.returnType)
+        }
+    }
+
+    /**
+     * Represents a query statement prepared in Dao implementation.
+     *
+     * @param fields This map holds all the member fields necessary for this query. The key is the
+     * corresponding parameter name in the defining query method. The value is a pair from the field
+     * declaration to definition.
+     * @param methodImpl The body of the query method implementation.
+     */
+    data class PreparedStmtQuery(
+            val fields: Map<String, Pair<FieldSpec, TypeSpec>>,
+            val methodImpl: MethodSpec) {
+        companion object {
+            // The key to be used in `fields` where the method requires a field that is not
+            // associated with any of its parameters
+            const val NO_PARAM_FIELD = "-"
+        }
+    }
+
+    private class InsertionMethodField(val entity: Entity, val onConflictText: String)
+        : SharedFieldSpec(
+            "insertionAdapterOf${Companion.typeNameToFieldName(entity.typeName)}",
+            RoomTypeNames.INSERTION_ADAPTER) {
+
+        override fun getUniqueKey(): String {
+            return "${entity.typeName} $onConflictText"
+        }
+
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+            builder.addModifiers(FINAL, PRIVATE)
+        }
+    }
+
+    class DeleteOrUpdateAdapterField(val entity: Entity, val methodPrefix: String)
+        : SharedFieldSpec(
+            "${methodPrefix}AdapterOf${Companion.typeNameToFieldName(entity.typeName)}",
+            RoomTypeNames.DELETE_OR_UPDATE_ADAPTER) {
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+            builder.addModifiers(PRIVATE, FINAL)
+        }
+
+        override fun getUniqueKey(): String {
+            return entity.typeName.toString() + methodPrefix
+        }
+    }
+
+    class PreparedStatementField(val method: QueryMethod) : SharedFieldSpec(
+            "preparedStmtOf${method.name.capitalize()}", RoomTypeNames.SHARED_SQLITE_STMT) {
+        override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
+            builder.addModifiers(PRIVATE, FINAL)
+        }
+
+        override fun getUniqueKey(): String {
+            return method.query.original
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
new file mode 100644
index 0000000..4acd316
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.DaoMethod
+import androidx.room.vo.Database
+import com.google.auto.common.MoreElements
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import stripNonJava
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PROTECTED
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.element.Modifier.VOLATILE
+
+/**
+ * Writes implementation of classes that were annotated with @Database.
+ */
+class DatabaseWriter(val database: Database) : ClassWriter(database.implTypeName) {
+    override fun createTypeSpecBuilder(): TypeSpec.Builder {
+        val builder = TypeSpec.classBuilder(database.implTypeName)
+        builder.apply {
+            addModifiers(PUBLIC)
+            superclass(database.typeName)
+            addMethod(createCreateOpenHelper())
+            addMethod(createCreateInvalidationTracker())
+            addMethod(createClearAllTables())
+        }
+        addDaoImpls(builder)
+        return builder
+    }
+
+    private fun createClearAllTables(): MethodSpec {
+        val scope = CodeGenScope(this)
+        return MethodSpec.methodBuilder("clearAllTables").apply {
+            val dbVar = scope.getTmpVar("_db")
+            addStatement("final $T $L = super.getOpenHelper().getWritableDatabase()",
+                    SupportDbTypeNames.DB, dbVar)
+            val deferVar = scope.getTmpVar("_supportsDeferForeignKeys")
+            if (database.enableForeignKeys) {
+                addStatement("boolean $L = $L.VERSION.SDK_INT >= $L.VERSION_CODES.LOLLIPOP",
+                        deferVar, AndroidTypeNames.BUILD, AndroidTypeNames.BUILD)
+            }
+            addAnnotation(Override::class.java)
+            addModifiers(PUBLIC)
+            returns(TypeName.VOID)
+            beginControlFlow("try").apply {
+                if (database.enableForeignKeys) {
+                    beginControlFlow("if (!$L)", deferVar).apply {
+                        addStatement("$L.execSQL($S)", dbVar, "PRAGMA foreign_keys = FALSE")
+                    }
+                    endControlFlow()
+                }
+                addStatement("super.beginTransaction()")
+                if (database.enableForeignKeys) {
+                    beginControlFlow("if ($L)", deferVar).apply {
+                        addStatement("$L.execSQL($S)", dbVar, "PRAGMA defer_foreign_keys = TRUE")
+                    }
+                    endControlFlow()
+                }
+                database.entities.sortedWith(EntityDeleteComparator()).forEach {
+                    addStatement("$L.execSQL($S)", dbVar, "DELETE FROM `${it.tableName}`")
+                }
+                addStatement("super.setTransactionSuccessful()")
+            }
+            nextControlFlow("finally").apply {
+                addStatement("super.endTransaction()")
+                if (database.enableForeignKeys) {
+                    beginControlFlow("if (!$L)", deferVar).apply {
+                        addStatement("$L.execSQL($S)", dbVar, "PRAGMA foreign_keys = TRUE")
+                    }
+                    endControlFlow()
+                }
+                addStatement("$L.query($S).close()", dbVar, "PRAGMA wal_checkpoint(FULL)")
+                addStatement("$L.execSQL($S)", dbVar, "VACUUM")
+            }
+            endControlFlow()
+        }.build()
+    }
+
+    private fun createCreateInvalidationTracker(): MethodSpec {
+        return MethodSpec.methodBuilder("createInvalidationTracker").apply {
+            addAnnotation(Override::class.java)
+            addModifiers(PROTECTED)
+            returns(RoomTypeNames.INVALIDATION_TRACKER)
+            val tableNames = database.entities.joinToString(",") {
+                "\"${it.tableName}\""
+            }
+            addStatement("return new $T(this, $L)", RoomTypeNames.INVALIDATION_TRACKER, tableNames)
+        }.build()
+    }
+
+    private fun addDaoImpls(builder: TypeSpec.Builder) {
+        val scope = CodeGenScope(this)
+        builder.apply {
+            database.daoMethods.forEach { method ->
+                val name = method.dao.typeName.simpleName().decapitalize().stripNonJava()
+                val fieldName = scope.getTmpVar("_$name")
+                val field = FieldSpec.builder(method.dao.typeName, fieldName,
+                        PRIVATE, VOLATILE).build()
+                addField(field)
+                addMethod(createDaoGetter(field, method))
+            }
+        }
+    }
+
+    private fun createDaoGetter(field: FieldSpec, method: DaoMethod): MethodSpec {
+        return MethodSpec.overriding(MoreElements.asExecutable(method.element)).apply {
+            beginControlFlow("if ($N != null)", field).apply {
+                addStatement("return $N", field)
+            }
+            nextControlFlow("else").apply {
+                beginControlFlow("synchronized(this)").apply {
+                    beginControlFlow("if($N == null)", field).apply {
+                        addStatement("$N = new $T(this)", field, method.dao.implTypeName)
+                    }
+                    endControlFlow()
+                    addStatement("return $N", field)
+                }
+                endControlFlow()
+            }
+            endControlFlow()
+        }.build()
+    }
+
+    private fun createCreateOpenHelper(): MethodSpec {
+        val scope = CodeGenScope(this)
+        return MethodSpec.methodBuilder("createOpenHelper").apply {
+            addModifiers(Modifier.PROTECTED)
+            addAnnotation(Override::class.java)
+            returns(SupportDbTypeNames.SQLITE_OPEN_HELPER)
+
+            val configParam = ParameterSpec.builder(RoomTypeNames.ROOM_DB_CONFIG,
+                    "configuration").build()
+            addParameter(configParam)
+
+            val openHelperVar = scope.getTmpVar("_helper")
+            val openHelperCode = scope.fork()
+            SQLiteOpenHelperWriter(database)
+                    .write(openHelperVar, configParam, openHelperCode)
+            addCode(openHelperCode.builder().build())
+            addStatement("return $L", openHelperVar)
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
new file mode 100644
index 0000000..69c8ec4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.EmbeddedField
+import androidx.room.vo.Entity
+import androidx.room.vo.FieldWithIndex
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import javax.lang.model.element.Modifier.PRIVATE
+
+class EntityCursorConverterWriter(val entity: Entity) : ClassWriter.SharedMethodSpec(
+        "entityCursorConverter_${entity.typeName.toString().stripNonJava()}") {
+    override fun getUniqueKey(): String {
+        return "generic_entity_converter_of_${entity.element.qualifiedName}"
+    }
+
+    override fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder) {
+        builder.apply {
+            val cursorParam = ParameterSpec
+                    .builder(AndroidTypeNames.CURSOR, "cursor").build()
+            addParameter(cursorParam)
+            addModifiers(PRIVATE)
+            returns(entity.typeName)
+            addCode(buildConvertMethodBody(writer, cursorParam))
+        }
+    }
+
+    private fun depth(parent: EmbeddedField?): Int {
+        return if (parent == null) {
+            0
+        } else {
+            1 + depth(parent.parent)
+        }
+    }
+
+    private fun buildConvertMethodBody(writer: ClassWriter, cursorParam: ParameterSpec): CodeBlock {
+        val scope = CodeGenScope(writer)
+        val entityVar = scope.getTmpVar("_entity")
+        scope.builder().apply {
+            scope.builder().addStatement("final $T $L", entity.typeName, entityVar)
+            val fieldsWithIndices = entity.fields.map {
+                val indexVar = scope.getTmpVar(
+                        "_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+                scope.builder().addStatement("final $T $L = $N.getColumnIndex($S)",
+                        TypeName.INT, indexVar, cursorParam, it.columnName)
+                FieldWithIndex(field = it,
+                        indexVar = indexVar,
+                        alwaysExists = false)
+            }
+            FieldReadWriteWriter.readFromCursor(
+                    outVar = entityVar,
+                    outPojo = entity,
+                    cursorVar = cursorParam.name,
+                    fieldsWithIndices = fieldsWithIndices,
+                    relationCollectors = emptyList(), // no relationship for entities
+                    scope = scope)
+            addStatement("return $L", entityVar)
+        }
+        return scope.builder().build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/EntityDeleteComparator.kt b/room/compiler/src/main/kotlin/androidx/room/writer/EntityDeleteComparator.kt
new file mode 100644
index 0000000..71828b4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/EntityDeleteComparator.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 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 androidx.room.writer
+
+import androidx.room.vo.Entity
+
+/**
+ * Sorts the entities by their foreign key dependencies. For example, when Entity A depends on
+ * Entity B, A is ordered before B.
+ */
+class EntityDeleteComparator : Comparator<Entity> {
+
+    override fun compare(lhs: Entity, rhs: Entity): Int {
+        val ltr = lhs.shouldBeDeletedAfter(rhs)
+        val rtl = rhs.shouldBeDeletedAfter(lhs)
+        return when {
+            ltr == rtl -> 0
+            ltr -> -1
+            rtl -> 1
+            else -> 0 // Never happens
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt
new file mode 100644
index 0000000..553bcad
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.ext.L
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.Entity
+import androidx.room.vo.FieldWithIndex
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PUBLIC
+
+class EntityDeletionAdapterWriter(val entity: Entity) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
+            superclass(ParameterizedTypeName.get(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER,
+                    entity.typeName)
+            )
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(PUBLIC)
+                val query = "DELETE FROM `${entity.tableName}` WHERE " +
+                        entity.primaryKey.fields.joinToString(" AND ") {
+                            "`${it.columnName}` = ?"
+                        }
+                addStatement("return $S", query)
+            }.build())
+            addMethod(MethodSpec.methodBuilder("bind").apply {
+                val bindScope = CodeGenScope(classWriter)
+                addAnnotation(Override::class.java)
+                val stmtParam = "stmt"
+                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
+                        stmtParam).build())
+                val valueParam = "value"
+                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
+                returns(TypeName.VOID)
+                addModifiers(PUBLIC)
+                val mapped = FieldWithIndex.byOrder(entity.primaryKey.fields)
+                FieldReadWriteWriter.bindToStatement(ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mapped,
+                        scope = bindScope)
+                addCode(bindScope.builder().build())
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
new file mode 100644
index 0000000..0bd56b4
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.ext.L
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.Entity
+import androidx.room.vo.FieldWithIndex
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PUBLIC
+
+class EntityInsertionAdapterWriter(val entity: Entity, val onConflict: String) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
+            superclass(
+                    ParameterizedTypeName.get(RoomTypeNames.INSERTION_ADAPTER, entity.typeName)
+            )
+
+            // If there is an auto-increment primary key with primitive type, we consider 0 as
+            // not set. For such fields, we must generate a slightly different insertion SQL.
+            val primitiveAutoGenerateField = if (entity.primaryKey.autoGenerateId) {
+                entity.primaryKey.fields.firstOrNull()?.let { field ->
+                    field.statementBinder?.typeMirror()?.let { binderType ->
+                        if (binderType.kind.isPrimitive) {
+                            field
+                        } else {
+                            null
+                        }
+                    }
+                }
+            } else {
+                null
+            }
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(PUBLIC)
+                val query =
+                        "INSERT OR $onConflict INTO `${entity.tableName}`(" +
+                                entity.fields.joinToString(",") {
+                                    "`${it.columnName}`"
+                                } + ") VALUES (" +
+                                entity.fields.joinToString(",") {
+                                    if (primitiveAutoGenerateField == it) {
+                                        "nullif(?, 0)"
+                                    } else {
+                                        "?"
+                                    }
+                                } + ")"
+                addStatement("return $S", query)
+            }.build())
+            addMethod(MethodSpec.methodBuilder("bind").apply {
+                val bindScope = CodeGenScope(classWriter)
+                addAnnotation(Override::class.java)
+                val stmtParam = "stmt"
+                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
+                        stmtParam).build())
+                val valueParam = "value"
+                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
+                returns(TypeName.VOID)
+                addModifiers(PUBLIC)
+                val mapped = FieldWithIndex.byOrder(entity.fields)
+                FieldReadWriteWriter.bindToStatement(
+                        ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mapped,
+                        scope = bindScope
+                )
+                addCode(bindScope.builder().build())
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
new file mode 100644
index 0000000..3363177
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
@@ -0,0 +1,86 @@
+/*
+ * 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 androidx.room.writer
+
+import androidx.room.ext.L
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.Entity
+import androidx.room.vo.FieldWithIndex
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PUBLIC
+
+class EntityUpdateAdapterWriter(val entity: Entity, val onConflict: String) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam: String): TypeSpec {
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$L", dbParam).apply {
+            superclass(ParameterizedTypeName.get(RoomTypeNames.DELETE_OR_UPDATE_ADAPTER,
+                    entity.typeName)
+            )
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(PUBLIC)
+                val query = "UPDATE OR $onConflict `${entity.tableName}` SET " +
+                        entity.fields.joinToString(",") { field ->
+                            "`${field.columnName}` = ?"
+                        } + " WHERE " + entity.primaryKey.fields.joinToString(" AND ") {
+                            "`${it.columnName}` = ?"
+                        }
+                addStatement("return $S", query)
+            }.build())
+            addMethod(MethodSpec.methodBuilder("bind").apply {
+                val bindScope = CodeGenScope(classWriter)
+                addAnnotation(Override::class.java)
+                val stmtParam = "stmt"
+                addParameter(ParameterSpec.builder(SupportDbTypeNames.SQLITE_STMT,
+                        stmtParam).build())
+                val valueParam = "value"
+                addParameter(ParameterSpec.builder(entity.typeName, valueParam).build())
+                returns(TypeName.VOID)
+                addModifiers(PUBLIC)
+                val mappedField = FieldWithIndex.byOrder(entity.fields)
+                FieldReadWriteWriter.bindToStatement(
+                        ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mappedField,
+                        scope = bindScope
+                )
+                val pkeyStart = entity.fields.size
+                val mappedPrimaryKeys = entity.primaryKey.fields.mapIndexed { index, field ->
+                    FieldWithIndex(field = field,
+                            indexVar = "${pkeyStart + index + 1}",
+                            alwaysExists = true)
+                }
+                FieldReadWriteWriter.bindToStatement(
+                        ownerVar = valueParam,
+                        stmtParamVar = stmtParam,
+                        fieldsWithIndices = mappedPrimaryKeys,
+                        scope = bindScope
+                )
+                addCode(bindScope.builder().build())
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/FieldReadWriteWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/FieldReadWriteWriter.kt
new file mode 100644
index 0000000..ba765b8
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/FieldReadWriteWriter.kt
@@ -0,0 +1,378 @@
+/*
+ * 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 androidx.room.writer
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.defaultValue
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.CallType
+import androidx.room.vo.Constructor
+import androidx.room.vo.EmbeddedField
+import androidx.room.vo.Field
+import androidx.room.vo.FieldWithIndex
+import androidx.room.vo.Pojo
+import androidx.room.vo.RelationCollector
+import com.squareup.javapoet.TypeName
+
+/**
+ * Handles writing a field into statement or reading it form statement.
+ */
+class FieldReadWriteWriter(fieldWithIndex: FieldWithIndex) {
+    val field = fieldWithIndex.field
+    val indexVar = fieldWithIndex.indexVar
+    val alwaysExists = fieldWithIndex.alwaysExists
+
+    companion object {
+        /*
+         * Get all parents including the ones which have grand children in this list but does not
+         * have any direct children in the list.
+         */
+        fun getAllParents(fields: List<Field>): Set<EmbeddedField> {
+            val allParents = mutableSetOf<EmbeddedField>()
+            fun addAllParents(field: Field) {
+                var parent = field.parent
+                while (parent != null) {
+                    if (allParents.add(parent)) {
+                        parent = parent.parent
+                    } else {
+                        break
+                    }
+                }
+            }
+            fields.forEach(::addAllParents)
+            return allParents
+        }
+
+        /**
+         * Convert the fields with indices into a Node tree so that we can recursively process
+         * them. This work is done here instead of parsing because the result may include arbitrary
+         * fields.
+         */
+        private fun createNodeTree(
+                rootVar: String,
+                fieldsWithIndices: List<FieldWithIndex>,
+                scope: CodeGenScope): Node {
+            val allParents = getAllParents(fieldsWithIndices.map { it.field })
+            val rootNode = Node(rootVar, null)
+            rootNode.directFields = fieldsWithIndices.filter { it.field.parent == null }
+            val parentNodes = allParents.associate {
+                Pair(it, Node(
+                        varName = scope.getTmpVar("_tmp${it.field.name.capitalize()}"),
+                        fieldParent = it))
+            }
+            parentNodes.values.forEach { node ->
+                val fieldParent = node.fieldParent!!
+                val grandParent = fieldParent.parent
+                val grandParentNode = grandParent?.let {
+                    parentNodes[it]
+                } ?: rootNode
+                node.directFields = fieldsWithIndices.filter { it.field.parent == fieldParent }
+                node.parentNode = grandParentNode
+                grandParentNode.subNodes.add(node)
+            }
+            return rootNode
+        }
+
+        fun bindToStatement(
+                ownerVar: String,
+                stmtParamVar: String,
+                fieldsWithIndices: List<FieldWithIndex>,
+                scope: CodeGenScope
+        ) {
+            fun visitNode(node: Node) {
+                fun bindWithDescendants() {
+                    node.directFields.forEach {
+                        FieldReadWriteWriter(it).bindToStatement(
+                                ownerVar = node.varName,
+                                stmtParamVar = stmtParamVar,
+                                scope = scope
+                        )
+                    }
+                    node.subNodes.forEach(::visitNode)
+                }
+
+                val fieldParent = node.fieldParent
+                if (fieldParent != null) {
+                    fieldParent.getter.writeGet(
+                            ownerVar = node.parentNode!!.varName,
+                            outVar = node.varName,
+                            builder = scope.builder()
+                    )
+                    scope.builder().apply {
+                        beginControlFlow("if($L != null)", node.varName).apply {
+                            bindWithDescendants()
+                        }
+                        nextControlFlow("else").apply {
+                            node.allFields().forEach {
+                                addStatement("$L.bindNull($L)", stmtParamVar, it.indexVar)
+                            }
+                        }
+                        endControlFlow()
+                    }
+                } else {
+                    bindWithDescendants()
+                }
+            }
+            visitNode(createNodeTree(ownerVar, fieldsWithIndices, scope))
+        }
+
+        /**
+         * Just constructs the given item, does NOT DECLARE. Declaration happens outside the
+         * reading statement since we may never read if the cursor does not have necessary
+         * columns.
+         */
+        private fun construct(
+                outVar: String,
+                constructor: Constructor?,
+                typeName: TypeName,
+                localVariableNames: Map<String, FieldWithIndex>,
+                localEmbeddeds: List<Node>, scope: CodeGenScope
+        ) {
+            if (constructor == null) {
+                // best hope code generation
+                scope.builder().apply {
+                    addStatement("$L = new $T()", outVar, typeName)
+                }
+                return
+            }
+            val variableNames = constructor.params.map { param ->
+                when (param) {
+                    is Constructor.FieldParam -> localVariableNames.entries.firstOrNull {
+                        it.value.field === param.field
+                    }?.key
+                    is Constructor.EmbeddedParam -> localEmbeddeds.firstOrNull {
+                        it.fieldParent === param.embedded
+                    }?.varName
+                    else -> null
+                }
+            }
+            val args = variableNames.joinToString(",") { it ?: "null" }
+            scope.builder().apply {
+                addStatement("$L = new $T($L)", outVar, typeName, args)
+            }
+        }
+
+        /**
+         * Reads the row into the given variable. It does not declare it but constructs it.
+         */
+        fun readFromCursor(
+                outVar: String,
+                outPojo: Pojo,
+                cursorVar: String,
+                fieldsWithIndices: List<FieldWithIndex>,
+                scope: CodeGenScope,
+                relationCollectors: List<RelationCollector>
+        ) {
+            fun visitNode(node: Node) {
+                val fieldParent = node.fieldParent
+                fun readNode() {
+                    // read constructor parameters into local fields
+                    val constructorFields = node.directFields.filter {
+                        it.field.setter.callType == CallType.CONSTRUCTOR
+                    }.associateBy { fwi ->
+                        FieldReadWriteWriter(fwi).readIntoTmpVar(cursorVar, scope)
+                    }
+                    // read decomposed fields
+                    node.subNodes.forEach(::visitNode)
+                    // construct the object
+                    if (fieldParent != null) {
+                        construct(outVar = node.varName,
+                                constructor = fieldParent.pojo.constructor,
+                                typeName = fieldParent.field.typeName,
+                                localEmbeddeds = node.subNodes,
+                                localVariableNames = constructorFields,
+                                scope = scope)
+                    } else {
+                        construct(outVar = node.varName,
+                                constructor = outPojo.constructor,
+                                typeName = outPojo.typeName,
+                                localEmbeddeds = node.subNodes,
+                                localVariableNames = constructorFields,
+                                scope = scope)
+                    }
+                    // ready any field that was not part of the constructor
+                    node.directFields.filterNot {
+                        it.field.setter.callType == CallType.CONSTRUCTOR
+                    }.forEach { fwi ->
+                        FieldReadWriteWriter(fwi).readFromCursor(
+                                ownerVar = node.varName,
+                                cursorVar = cursorVar,
+                                scope = scope)
+                    }
+                    // assign relationship fields which will be read later
+                    relationCollectors.filter { (relation) ->
+                        relation.field.parent === fieldParent
+                    }.forEach {
+                        it.writeReadParentKeyCode(
+                                cursorVarName = cursorVar,
+                                itemVar = node.varName,
+                                fieldsWithIndices = fieldsWithIndices,
+                                scope = scope)
+                    }
+                    // assign sub modes to fields if they were not part of the constructor.
+                    node.subNodes.map {
+                        val setter = it.fieldParent?.setter
+                        if (setter != null && setter.callType != CallType.CONSTRUCTOR) {
+                            Pair(it.varName, setter)
+                        } else {
+                            null
+                        }
+                    }.filterNotNull().forEach { (varName, setter) ->
+                        setter.writeSet(
+                                ownerVar = node.varName,
+                                inVar = varName,
+                                builder = scope.builder())
+                    }
+                }
+                if (fieldParent == null) {
+                    // root element
+                    // always declared by the caller so we don't declare this
+                    readNode()
+                } else {
+                    // always declare, we'll set below
+                    scope.builder().addStatement("final $T $L", fieldParent.pojo.typeName,
+                                        node.varName)
+                    if (fieldParent.nonNull) {
+                        readNode()
+                    } else {
+                        val myDescendants = node.allFields()
+                        val allNullCheck = myDescendants.joinToString(" && ") {
+                            if (it.alwaysExists) {
+                                "$cursorVar.isNull(${it.indexVar})"
+                            } else {
+                                "( ${it.indexVar} == -1 || $cursorVar.isNull(${it.indexVar}))"
+                            }
+                        }
+                        scope.builder().apply {
+                            beginControlFlow("if (! ($L))", allNullCheck).apply {
+                                readNode()
+                            }
+                            nextControlFlow(" else ").apply {
+                                addStatement("$L = null", node.varName)
+                            }
+                            endControlFlow()
+                        }
+                    }
+                }
+            }
+            visitNode(createNodeTree(outVar, fieldsWithIndices, scope))
+        }
+    }
+
+    /**
+     * @param ownerVar The entity / pojo that owns this field. It must own this field! (not the
+     * container pojo)
+     * @param stmtParamVar The statement variable
+     * @param scope The code generation scope
+     */
+    private fun bindToStatement(ownerVar: String, stmtParamVar: String, scope: CodeGenScope) {
+        field.statementBinder?.let { binder ->
+            val varName = if (field.getter.callType == CallType.FIELD) {
+                "$ownerVar.${field.name}"
+            } else {
+                "$ownerVar.${field.getter.name}()"
+            }
+            binder.bindToStmt(stmtParamVar, indexVar, varName, scope)
+        }
+    }
+
+    /**
+     * @param ownerVar The entity / pojo that owns this field. It must own this field (not the
+     * container pojo)
+     * @param cursorVar The cursor variable
+     * @param scope The code generation scope
+     */
+    private fun readFromCursor(ownerVar: String, cursorVar: String, scope: CodeGenScope) {
+        fun toRead() {
+            field.cursorValueReader?.let { reader ->
+                scope.builder().apply {
+                    when (field.setter.callType) {
+                        CallType.FIELD -> {
+                            reader.readFromCursor("$ownerVar.${field.getter.name}", cursorVar,
+                                    indexVar, scope)
+                        }
+                        CallType.METHOD -> {
+                            val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+                            addStatement("final $T $L", field.getter.type.typeName(), tmpField)
+                            reader.readFromCursor(tmpField, cursorVar, indexVar, scope)
+                            addStatement("$L.$L($L)", ownerVar, field.setter.name, tmpField)
+                        }
+                        CallType.CONSTRUCTOR -> {
+                            // no-op
+                        }
+                    }
+                }
+            }
+        }
+        if (alwaysExists) {
+            toRead()
+        } else {
+            scope.builder().apply {
+                beginControlFlow("if ($L != -1)", indexVar).apply {
+                    toRead()
+                }
+                endControlFlow()
+            }
+        }
+    }
+
+    /**
+     * Reads the value into a temporary local variable.
+     */
+    fun readIntoTmpVar(cursorVar: String, scope: CodeGenScope): String {
+        val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+        val typeName = field.getter.type.typeName()
+        scope.builder().apply {
+            addStatement("final $T $L", typeName, tmpField)
+            if (alwaysExists) {
+                field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
+            } else {
+                beginControlFlow("if ($L == -1)", indexVar).apply {
+                    addStatement("$L = $L", tmpField, typeName.defaultValue())
+                }
+                nextControlFlow("else").apply {
+                    field.cursorValueReader?.readFromCursor(tmpField, cursorVar, indexVar, scope)
+                }
+                endControlFlow()
+            }
+        }
+        return tmpField
+    }
+
+    /**
+     * On demand node which is created based on the fields that were passed into this class.
+     */
+    private class Node(
+            // root for me
+            val varName: String,
+            // set if I'm a FieldParent
+            val fieldParent: EmbeddedField?
+    ) {
+        // whom do i belong
+        var parentNode: Node? = null
+        // these fields are my direct fields
+        lateinit var directFields: List<FieldWithIndex>
+        // these nodes are under me
+        val subNodes = mutableListOf<Node>()
+
+        fun allFields(): List<FieldWithIndex> {
+            return directFields + subNodes.flatMap { it.allFields() }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/PreparedStatementWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/PreparedStatementWriter.kt
new file mode 100644
index 0000000..e035f8f
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/PreparedStatementWriter.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.writer
+
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.solver.CodeGenScope
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+
+/**
+ * Creates anonymous classes for RoomTypeNames#SHARED_SQLITE_STMT.
+ */
+class PreparedStatementWriter(val queryWriter: QueryWriter) {
+    fun createAnonymous(classWriter: ClassWriter, dbParam: FieldSpec): TypeSpec {
+        val scope = CodeGenScope(classWriter)
+        @Suppress("RemoveSingleExpressionStringTemplate")
+        return TypeSpec.anonymousClassBuilder("$N", dbParam).apply {
+            superclass(RoomTypeNames.SHARED_SQLITE_STMT)
+            addMethod(MethodSpec.methodBuilder("createQuery").apply {
+                addAnnotation(Override::class.java)
+                returns(ClassName.get("java.lang", "String"))
+                addModifiers(Modifier.PUBLIC)
+                val queryName = scope.getTmpVar("_query")
+                val queryGenScope = scope.fork()
+                queryWriter.prepareQuery(queryName, queryGenScope)
+                addCode(queryGenScope.builder().build())
+                addStatement("return $L", queryName)
+            }.build())
+        }.build()
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
new file mode 100644
index 0000000..3bf902a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.ext.L
+import androidx.room.ext.RoomTypeNames.ROOM_SQL_QUERY
+import androidx.room.ext.RoomTypeNames.STRING_UTIL
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.parser.ParsedQuery
+import androidx.room.parser.Section
+import androidx.room.parser.SectionType.BIND_VAR
+import androidx.room.parser.SectionType.NEWLINE
+import androidx.room.parser.SectionType.TEXT
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.QueryMethod
+import androidx.room.vo.QueryParameter
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+
+/**
+ * Writes the SQL query and arguments for a QueryMethod.
+ */
+class QueryWriter constructor(val parameters: List<QueryParameter>,
+                              val sectionToParamMapping: List<Pair<Section, QueryParameter?>>,
+                              val query: ParsedQuery) {
+
+    constructor(queryMethod: QueryMethod) : this(queryMethod.parameters,
+            queryMethod.sectionToParamMapping, queryMethod.query) {
+    }
+
+    fun prepareReadAndBind(outSqlQueryName: String, outRoomSQLiteQueryVar: String,
+                           scope: CodeGenScope) {
+        val listSizeVars = createSqlQueryAndArgs(outSqlQueryName, outRoomSQLiteQueryVar, scope)
+        bindArgs(outRoomSQLiteQueryVar, listSizeVars, scope)
+    }
+
+    fun prepareQuery(
+            outSqlQueryName: String, scope: CodeGenScope): List<Pair<QueryParameter, String>> {
+        return createSqlQueryAndArgs(outSqlQueryName, null, scope)
+    }
+
+    private fun createSqlQueryAndArgs(
+            outSqlQueryName: String,
+            outArgsName: String?,
+            scope: CodeGenScope
+    ): List<Pair<QueryParameter, String>> {
+        val listSizeVars = arrayListOf<Pair<QueryParameter, String>>()
+        val varargParams = parameters
+                .filter { it.queryParamAdapter?.isMultiple ?: false }
+        val sectionToParamMapping = sectionToParamMapping
+        val knownQueryArgsCount = sectionToParamMapping.filterNot {
+            it.second?.queryParamAdapter?.isMultiple ?: false
+        }.size
+        scope.builder().apply {
+            if (varargParams.isNotEmpty()) {
+                val stringBuilderVar = scope.getTmpVar("_stringBuilder")
+                addStatement("$T $L = $T.newStringBuilder()",
+                        ClassName.get(StringBuilder::class.java), stringBuilderVar, STRING_UTIL)
+                query.sections.forEach {
+                    when (it.type) {
+                        TEXT -> addStatement("$L.append($S)", stringBuilderVar, it.text)
+                        NEWLINE -> addStatement("$L.append($S)", stringBuilderVar, "\n")
+                        BIND_VAR -> {
+                            // If it is null, will be reported as error before. We just try out
+                            // best to generate as much code as possible.
+                            sectionToParamMapping.firstOrNull { mapping ->
+                                mapping.first == it
+                            }?.let { pair ->
+                                if (pair.second?.queryParamAdapter?.isMultiple ?: false) {
+                                    val tmpCount = scope.getTmpVar("_inputSize")
+                                    listSizeVars.add(Pair(pair.second!!, tmpCount))
+                                    pair.second
+                                            ?.queryParamAdapter
+                                            ?.getArgCount(pair.second!!.name, tmpCount, scope)
+                                    addStatement("$T.appendPlaceholders($L, $L)",
+                                            STRING_UTIL, stringBuilderVar, tmpCount)
+                                } else {
+                                    addStatement("$L.append($S)", stringBuilderVar, "?")
+                                }
+                            }
+                        }
+                    }
+                }
+
+                addStatement("final $T $L = $L.toString()", String::class.typeName(),
+                        outSqlQueryName, stringBuilderVar)
+                if (outArgsName != null) {
+                    val argCount = scope.getTmpVar("_argCount")
+                    addStatement("final $T $L = $L$L", TypeName.INT, argCount, knownQueryArgsCount,
+                            listSizeVars.joinToString("") { " + ${it.second}" })
+                    addStatement("final $T $L = $T.acquire($L, $L)",
+                            ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
+                            argCount)
+                }
+            } else {
+                addStatement("final $T $L = $S", String::class.typeName(),
+                        outSqlQueryName, query.queryWithReplacedBindParams)
+                if (outArgsName != null) {
+                    addStatement("final $T $L = $T.acquire($L, $L)",
+                            ROOM_SQL_QUERY, outArgsName, ROOM_SQL_QUERY, outSqlQueryName,
+                            knownQueryArgsCount)
+                }
+            }
+        }
+        return listSizeVars
+    }
+
+    fun bindArgs(
+            outArgsName: String,
+            listSizeVars: List<Pair<QueryParameter, String>>,
+            scope: CodeGenScope
+    ) {
+        if (parameters.isEmpty()) {
+            return
+        }
+        scope.builder().apply {
+            val argIndex = scope.getTmpVar("_argIndex")
+            addStatement("$T $L = $L", TypeName.INT, argIndex, 1)
+            // # of bindings with 1 placeholder
+            var constInputs = 0
+            // variable names for size of the bindings that have multiple  args
+            val varInputs = arrayListOf<String>()
+            sectionToParamMapping.forEach { pair ->
+                // reset the argIndex to the correct start index
+                if (constInputs > 0 || varInputs.isNotEmpty()) {
+                    addStatement("$L = $L$L", argIndex,
+                            if (constInputs > 0) (1 + constInputs) else "1",
+                            varInputs.joinToString("") { " + $it" })
+                }
+                val param = pair.second
+                param?.let {
+                    param.queryParamAdapter?.bindToStmt(param.name, outArgsName, argIndex, scope)
+                }
+                // add these to the list so that we can use them to calculate the next count.
+                val sizeVar = listSizeVars.firstOrNull { it.first == param }
+                if (sizeVar == null) {
+                    constInputs ++
+                } else {
+                    varInputs.add(sizeVar.second)
+                }
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
new file mode 100644
index 0000000..b8b1ca1
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/RelationCollectorMethodWriter.kt
@@ -0,0 +1,162 @@
+/*
+ * 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 androidx.room.writer
+
+import androidx.room.ext.AndroidTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.RelationCollector
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import stripNonJava
+import javax.lang.model.element.Modifier
+
+/**
+ * Writes the method that fetches the relations of a POJO and assigns them into the given map.
+ */
+class RelationCollectorMethodWriter(private val collector: RelationCollector)
+    : ClassWriter.SharedMethodSpec(
+        "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
+                "As${collector.relation.pojoTypeName.toString().stripNonJava()}") {
+    companion object {
+        val KEY_SET_VARIABLE = "__mapKeySet"
+    }
+    override fun getUniqueKey(): String {
+        val relation = collector.relation
+        return "RelationCollectorMethodWriter" +
+                "-${collector.mapTypeName}" +
+                "-${relation.entity.typeName}" +
+                "-${relation.entityField.columnName}" +
+                "-${relation.pojoTypeName}" +
+                "-${relation.createLoadAllSql()}"
+    }
+
+    override fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder) {
+        val scope = CodeGenScope(writer)
+        val relation = collector.relation
+
+        val param = ParameterSpec.builder(collector.mapTypeName, "_map")
+                .addModifiers(Modifier.FINAL)
+                .build()
+        val sqlQueryVar = scope.getTmpVar("_sql")
+        val keySetVar = KEY_SET_VARIABLE
+
+        val cursorVar = "_cursor"
+        val itemKeyIndexVar = "_itemKeyIndex"
+        val stmtVar = scope.getTmpVar("_stmt")
+        scope.builder().apply {
+
+            val keySetType = ParameterizedTypeName.get(
+                    ClassName.get(Set::class.java), collector.keyTypeName
+            )
+            addStatement("final $T $L = $N.keySet()", keySetType, keySetVar, param)
+            beginControlFlow("if ($L.isEmpty())", keySetVar).apply {
+                addStatement("return")
+            }
+            endControlFlow()
+            addStatement("// check if the size is too big, if so divide")
+            beginControlFlow("if($N.size() > $T.MAX_BIND_PARAMETER_CNT)",
+                    param, RoomTypeNames.ROOM_DB).apply {
+                // divide it into chunks
+                val tmpMapVar = scope.getTmpVar("_tmpInnerMap")
+                addStatement("$T $L = new $T($L.MAX_BIND_PARAMETER_CNT)",
+                        collector.mapTypeName, tmpMapVar,
+                        collector.mapTypeName, RoomTypeNames.ROOM_DB)
+                val mapIndexVar = scope.getTmpVar("_mapIndex")
+                val tmpIndexVar = scope.getTmpVar("_tmpIndex")
+                val limitVar = scope.getTmpVar("_limit")
+                addStatement("$T $L = 0", TypeName.INT, mapIndexVar)
+                addStatement("$T $L = 0", TypeName.INT, tmpIndexVar)
+                addStatement("final $T $L = $N.size()", TypeName.INT, limitVar, param)
+                beginControlFlow("while($L < $L)", mapIndexVar, limitVar).apply {
+                    addStatement("$L.put($N.keyAt($L), $N.valueAt($L))",
+                            tmpMapVar, param, mapIndexVar, param, mapIndexVar)
+                    addStatement("$L++", mapIndexVar)
+                    addStatement("$L++", tmpIndexVar)
+                    beginControlFlow("if($L == $T.MAX_BIND_PARAMETER_CNT)",
+                            tmpIndexVar, RoomTypeNames.ROOM_DB).apply {
+                        // recursively load that batch
+                        addStatement("$L($L)", methodName, tmpMapVar)
+                        // clear nukes the backing data hence we create a new one
+                        addStatement("$L = new $T($T.MAX_BIND_PARAMETER_CNT)",
+                                tmpMapVar, collector.mapTypeName, RoomTypeNames.ROOM_DB)
+                        addStatement("$L = 0", tmpIndexVar)
+                    }.endControlFlow()
+                }.endControlFlow()
+                beginControlFlow("if($L > 0)", tmpIndexVar).apply {
+                    // load the last batch
+                    addStatement("$L($L)", methodName, tmpMapVar)
+                }.endControlFlow()
+                addStatement("return")
+            }.endControlFlow()
+            collector.queryWriter.prepareReadAndBind(sqlQueryVar, stmtVar, scope)
+
+            addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
+                    DaoWriter.dbField, stmtVar)
+
+            beginControlFlow("try").apply {
+                addStatement("final $T $L = $L.getColumnIndex($S)",
+                        TypeName.INT, itemKeyIndexVar, cursorVar, relation.entityField.columnName)
+
+                beginControlFlow("if ($L == -1)", itemKeyIndexVar).apply {
+                    addStatement("return")
+                }
+                endControlFlow()
+
+                collector.rowAdapter.onCursorReady(cursorVar, scope)
+                val tmpVarName = scope.getTmpVar("_item")
+                beginControlFlow("while($L.moveToNext())", cursorVar).apply {
+                    // read key from the cursor
+                    collector.readKey(
+                            cursorVarName = cursorVar,
+                            indexVar = itemKeyIndexVar,
+                            scope = scope
+                    ) { keyVar ->
+                        val collectionVar = scope.getTmpVar("_tmpCollection")
+                        addStatement("$T $L = $N.get($L)", collector.collectionTypeName,
+                                collectionVar, param, keyVar)
+                        beginControlFlow("if ($L != null)", collectionVar).apply {
+                            addStatement("final $T $L", relation.pojoTypeName, tmpVarName)
+                            collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
+                            addStatement("$L.add($L)", collectionVar, tmpVarName)
+                        }
+                        endControlFlow()
+                    }
+                }
+                endControlFlow()
+                collector.rowAdapter.onCursorFinished()?.invoke(scope)
+            }
+            nextControlFlow("finally").apply {
+                addStatement("$L.close()", cursorVar)
+            }
+            endControlFlow()
+        }
+        builder.apply {
+            addModifiers(Modifier.PRIVATE)
+            addParameter(param)
+            returns(TypeName.VOID)
+            addCode(scope.builder().build())
+        }
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/SQLiteOpenHelperWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/SQLiteOpenHelperWriter.kt
new file mode 100644
index 0000000..17501dc
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/SQLiteOpenHelperWriter.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.annotation.VisibleForTesting
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.Database
+import androidx.room.vo.Entity
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier.PROTECTED
+import javax.lang.model.element.Modifier.PUBLIC
+
+/**
+ * Create an open helper using SupportSQLiteOpenHelperFactory
+ */
+class SQLiteOpenHelperWriter(val database: Database) {
+    fun write(outVar: String, configuration: ParameterSpec, scope: CodeGenScope) {
+        scope.builder().apply {
+            val sqliteConfigVar = scope.getTmpVar("_sqliteConfig")
+            val callbackVar = scope.getTmpVar("_openCallback")
+            addStatement("final $T $L = new $T($N, $L, $S, $S)",
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CALLBACK,
+                    callbackVar, RoomTypeNames.OPEN_HELPER, configuration,
+                    createOpenCallback(scope), database.identityHash, database.legacyIdentityHash)
+            // build configuration
+            addStatement(
+                    """
+                    final $T $L = $T.builder($N.context)
+                    .name($N.name)
+                    .callback($L)
+                    .build()
+                    """.trimIndent(),
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG, sqliteConfigVar,
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER_CONFIG,
+                    configuration, configuration, callbackVar)
+            addStatement("final $T $N = $N.sqliteOpenHelperFactory.create($L)",
+                    SupportDbTypeNames.SQLITE_OPEN_HELPER, outVar,
+                    configuration, sqliteConfigVar)
+        }
+    }
+
+    private fun createOpenCallback(scope: CodeGenScope): TypeSpec {
+        return TypeSpec.anonymousClassBuilder(L, database.version).apply {
+            superclass(RoomTypeNames.OPEN_HELPER_DELEGATE)
+            addMethod(createCreateAllTables())
+            addMethod(createDropAllTables())
+            addMethod(createOnCreate(scope.fork()))
+            addMethod(createOnOpen(scope.fork()))
+            addMethod(createValidateMigration(scope.fork()))
+        }.build()
+    }
+
+    private fun createValidateMigration(scope: CodeGenScope): MethodSpec {
+        return MethodSpec.methodBuilder("validateMigration").apply {
+            addModifiers(PROTECTED)
+            addAnnotation(Override::class.java)
+            val dbParam = ParameterSpec.builder(SupportDbTypeNames.DB, "_db").build()
+            addParameter(dbParam)
+            database.entities.forEach { entity ->
+                val methodScope = scope.fork()
+                TableInfoValidationWriter(entity).write(dbParam, methodScope)
+                addCode(methodScope.builder().build())
+            }
+        }.build()
+    }
+
+    private fun createOnCreate(scope: CodeGenScope): MethodSpec {
+        return MethodSpec.methodBuilder("onCreate").apply {
+            addModifiers(PROTECTED)
+            addAnnotation(Override::class.java)
+            addParameter(SupportDbTypeNames.DB, "_db")
+            invokeCallbacks(scope, "onCreate")
+        }.build()
+    }
+
+    private fun createOnOpen(scope: CodeGenScope): MethodSpec {
+        return MethodSpec.methodBuilder("onOpen").apply {
+            addModifiers(PUBLIC)
+            addAnnotation(Override::class.java)
+            addParameter(SupportDbTypeNames.DB, "_db")
+            addStatement("mDatabase = _db")
+            if (database.enableForeignKeys) {
+                addStatement("_db.execSQL($S)", "PRAGMA foreign_keys = ON")
+            }
+            addStatement("internalInitInvalidationTracker(_db)")
+            invokeCallbacks(scope, "onOpen")
+        }.build()
+    }
+
+    private fun createCreateAllTables(): MethodSpec {
+        return MethodSpec.methodBuilder("createAllTables").apply {
+            addModifiers(PUBLIC)
+            addAnnotation(Override::class.java)
+            addParameter(SupportDbTypeNames.DB, "_db")
+            database.bundle.buildCreateQueries().forEach {
+                addStatement("_db.execSQL($S)", it)
+            }
+        }.build()
+    }
+
+    private fun createDropAllTables(): MethodSpec {
+        return MethodSpec.methodBuilder("dropAllTables").apply {
+            addModifiers(PUBLIC)
+            addAnnotation(Override::class.java)
+            addParameter(SupportDbTypeNames.DB, "_db")
+            database.entities.forEach {
+                addStatement("_db.execSQL($S)", createDropTableQuery(it))
+            }
+        }.build()
+    }
+
+    private fun MethodSpec.Builder.invokeCallbacks(scope: CodeGenScope, methodName: String) {
+        val iVar = scope.getTmpVar("_i")
+        val sizeVar = scope.getTmpVar("_size")
+        beginControlFlow("if (mCallbacks != null)").apply {
+            beginControlFlow("for (int $N = 0, $N = mCallbacks.size(); $N < $N; $N++)",
+                    iVar, sizeVar, iVar, sizeVar, iVar).apply {
+                addStatement("mCallbacks.get($N).$N(_db)", iVar, methodName)
+            }
+            endControlFlow()
+        }
+        endControlFlow()
+    }
+
+    @VisibleForTesting
+    fun createQuery(entity: Entity): String {
+        return entity.createTableQuery
+    }
+
+    @VisibleForTesting
+    fun createDropTableQuery(entity: Entity): String {
+        return "DROP TABLE IF EXISTS `${entity.tableName}`"
+    }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
new file mode 100644
index 0000000..1b9964a
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
@@ -0,0 +1,114 @@
+/*
+ * 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 androidx.room.writer
+
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.solver.CodeGenScope
+import androidx.room.vo.Entity
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import stripNonJava
+import java.util.Arrays
+import java.util.HashMap
+import java.util.HashSet
+
+class TableInfoValidationWriter(val entity: Entity) {
+    fun write(dbParam: ParameterSpec, scope: CodeGenScope) {
+        val suffix = entity.tableName.stripNonJava().capitalize()
+        val expectedInfoVar = scope.getTmpVar("_info$suffix")
+        scope.builder().apply {
+            val columnListVar = scope.getTmpVar("_columns$suffix")
+            val columnListType = ParameterizedTypeName.get(HashMap::class.typeName(),
+                    CommonTypeNames.STRING, RoomTypeNames.TABLE_INFO_COLUMN)
+
+            addStatement("final $T $L = new $T($L)", columnListType, columnListVar,
+                    columnListType, entity.fields.size)
+            entity.fields.forEach { field ->
+                addStatement("$L.put($S, new $T($S, $S, $L, $L))",
+                        columnListVar, field.columnName, RoomTypeNames.TABLE_INFO_COLUMN,
+                        /*name*/ field.columnName,
+                        /*type*/ field.affinity?.name ?: SQLTypeAffinity.TEXT.name,
+                        /*nonNull*/ field.nonNull,
+                        /*pkeyPos*/ entity.primaryKey.fields.indexOf(field) + 1)
+            }
+
+            val foreignKeySetVar = scope.getTmpVar("_foreignKeys$suffix")
+            val foreignKeySetType = ParameterizedTypeName.get(HashSet::class.typeName(),
+                    RoomTypeNames.TABLE_INFO_FOREIGN_KEY)
+            addStatement("final $T $L = new $T($L)", foreignKeySetType, foreignKeySetVar,
+                    foreignKeySetType, entity.foreignKeys.size)
+            entity.foreignKeys.forEach {
+                val myColumnNames = it.childFields
+                        .joinToString(",") { "\"${it.columnName}\"" }
+                val refColumnNames = it.parentColumns
+                        .joinToString(",") { "\"$it\"" }
+                addStatement("$L.add(new $T($S, $S, $S," +
+                        "$T.asList($L), $T.asList($L)))", foreignKeySetVar,
+                        RoomTypeNames.TABLE_INFO_FOREIGN_KEY,
+                        /*parent table*/ it.parentTable,
+                        /*on delete*/ it.onDelete.sqlName,
+                        /*on update*/ it.onUpdate.sqlName,
+                        Arrays::class.typeName(),
+                        /*parent names*/ myColumnNames,
+                        Arrays::class.typeName(),
+                        /*parent column names*/ refColumnNames)
+            }
+
+            val indicesSetVar = scope.getTmpVar("_indices$suffix")
+            val indicesType = ParameterizedTypeName.get(HashSet::class.typeName(),
+                    RoomTypeNames.TABLE_INFO_INDEX)
+            addStatement("final $T $L = new $T($L)", indicesType, indicesSetVar,
+                    indicesType, entity.indices.size)
+            entity.indices.forEach { index ->
+                val columnNames = index.fields
+                        .joinToString(",") { "\"${it.columnName}\"" }
+                addStatement("$L.add(new $T($S, $L, $T.asList($L)))",
+                        indicesSetVar,
+                        RoomTypeNames.TABLE_INFO_INDEX,
+                        index.name,
+                        index.unique,
+                        Arrays::class.typeName(),
+                        columnNames)
+            }
+
+            addStatement("final $T $L = new $T($S, $L, $L, $L)",
+                    RoomTypeNames.TABLE_INFO, expectedInfoVar, RoomTypeNames.TABLE_INFO,
+                    entity.tableName, columnListVar, foreignKeySetVar, indicesSetVar)
+
+            val existingVar = scope.getTmpVar("_existing$suffix")
+            addStatement("final $T $L = $T.read($N, $S)",
+                    RoomTypeNames.TABLE_INFO, existingVar, RoomTypeNames.TABLE_INFO,
+                    dbParam, entity.tableName)
+
+            beginControlFlow("if (! $L.equals($L))", expectedInfoVar, existingVar).apply {
+                addStatement("throw new $T($S + $L + $S + $L)",
+                        IllegalStateException::class.typeName(),
+                        "Migration didn't properly handle ${entity.tableName}" +
+                                "(${entity.element.qualifiedName}).\n Expected:\n",
+                        expectedInfoVar, "\n Found:\n", existingVar)
+            }
+            endControlFlow()
+        }
+    }
+}
diff --git a/room/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/room/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
index f628f64..ee7c7ee 100644
--- a/room/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
+++ b/room/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -1 +1 @@
-android.arch.persistence.room.RoomProcessor
+androidx.room.RoomProcessor
diff --git a/room/compiler/src/test/data/common/input/Book.java b/room/compiler/src/test/data/common/input/Book.java
index 00cf8b6..bbad33d 100644
--- a/room/compiler/src/test/data/common/input/Book.java
+++ b/room/compiler/src/test/data/common/input/Book.java
@@ -15,7 +15,7 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 @Entity
 public class Book {
     @PrimaryKey
diff --git a/room/compiler/src/test/data/common/input/ComputableLiveData.java b/room/compiler/src/test/data/common/input/ComputableLiveData.java
index f135244..da5a15e 100644
--- a/room/compiler/src/test/data/common/input/ComputableLiveData.java
+++ b/room/compiler/src/test/data/common/input/ComputableLiveData.java
@@ -1,6 +1,6 @@
 //ComputableLiveData interface for tests
-package android.arch.lifecycle;
-import android.arch.lifecycle.LiveData;
+package androidx.lifecycle;
+import androidx.lifecycle.LiveData;
 public abstract class ComputableLiveData<T> {
     public ComputableLiveData(){}
     abstract protected T compute();
diff --git a/room/compiler/src/test/data/common/input/DataSource.java b/room/compiler/src/test/data/common/input/DataSource.java
index 9f51539..157750a 100644
--- a/room/compiler/src/test/data/common/input/DataSource.java
+++ b/room/compiler/src/test/data/common/input/DataSource.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.arch.paging;
+package androidx.paging;
 
 abstract public class DataSource<K, T> {
     public interface Factory<Key, Value> {
diff --git a/room/compiler/src/test/data/common/input/LiveData.java b/room/compiler/src/test/data/common/input/LiveData.java
index 3aea6ac..c961d1c 100644
--- a/room/compiler/src/test/data/common/input/LiveData.java
+++ b/room/compiler/src/test/data/common/input/LiveData.java
@@ -1,4 +1,4 @@
 //LiveData interface for tests
-package android.arch.lifecycle;
+package androidx.lifecycle;
 public class LiveData<T> {
 }
diff --git a/room/compiler/src/test/data/common/input/MultiPKeyEntity.java b/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
index 59570ad..67c3bbf 100644
--- a/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
+++ b/room/compiler/src/test/data/common/input/MultiPKeyEntity.java
@@ -15,8 +15,8 @@
  */
 
 package foo.bar;
-import android.support.annotation.NonNull;
-import android.arch.persistence.room.*;
+import androidx.annotation.NonNull;
+import androidx.room.*;
 @Entity(primaryKeys = {"name", "lastName"})
 public class MultiPKeyEntity {
     @NonNull
diff --git a/room/compiler/src/test/data/common/input/NotAnEntity.java b/room/compiler/src/test/data/common/input/NotAnEntity.java
index 90c1dd4..d8b4749 100644
--- a/room/compiler/src/test/data/common/input/NotAnEntity.java
+++ b/room/compiler/src/test/data/common/input/NotAnEntity.java
@@ -15,7 +15,7 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 public class NotAnEntity {
     int bookId;
     int uid;
diff --git a/room/compiler/src/test/data/common/input/PositionalDataSource.java b/room/compiler/src/test/data/common/input/PositionalDataSource.java
index 7cff0d7..ff5338a 100644
--- a/room/compiler/src/test/data/common/input/PositionalDataSource.java
+++ b/room/compiler/src/test/data/common/input/PositionalDataSource.java
@@ -1,4 +1,4 @@
-package android.arch.paging;
+package androidx.paging;
 
 public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
 
diff --git a/room/compiler/src/test/data/common/input/Rx2Room.java b/room/compiler/src/test/data/common/input/Rx2Room.java
index da474fd..47b9553 100644
--- a/room/compiler/src/test/data/common/input/Rx2Room.java
+++ b/room/compiler/src/test/data/common/input/Rx2Room.java
@@ -1,5 +1,5 @@
 // mock rx2 helper
-package android.arch.persistence.room;
+package androidx.room;
 
 class RxRoom {
 }
diff --git a/room/compiler/src/test/data/common/input/User.java b/room/compiler/src/test/data/common/input/User.java
index ce487d3..a8b7feb 100644
--- a/room/compiler/src/test/data/common/input/User.java
+++ b/room/compiler/src/test/data/common/input/User.java
@@ -15,7 +15,7 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 @Entity
 public class User {
     @PrimaryKey
diff --git a/room/compiler/src/test/data/daoWriter/input/ComplexDao.java b/room/compiler/src/test/data/daoWriter/input/ComplexDao.java
index 89859e5..037261e 100644
--- a/room/compiler/src/test/data/daoWriter/input/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/input/ComplexDao.java
@@ -15,9 +15,9 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 import java.util.List;
-import android.arch.lifecycle.LiveData;
+import androidx.lifecycle.LiveData;
 @Dao
 abstract class ComplexDao {
     static class FullName {
diff --git a/room/compiler/src/test/data/daoWriter/input/DeletionDao.java b/room/compiler/src/test/data/daoWriter/input/DeletionDao.java
index 997f290..a114c30 100644
--- a/room/compiler/src/test/data/daoWriter/input/DeletionDao.java
+++ b/room/compiler/src/test/data/daoWriter/input/DeletionDao.java
@@ -15,7 +15,7 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 import java.util.List;
 
 @Dao
diff --git a/room/compiler/src/test/data/daoWriter/input/UpdateDao.java b/room/compiler/src/test/data/daoWriter/input/UpdateDao.java
index 8041268..def944b 100644
--- a/room/compiler/src/test/data/daoWriter/input/UpdateDao.java
+++ b/room/compiler/src/test/data/daoWriter/input/UpdateDao.java
@@ -15,7 +15,7 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 import java.util.List;
 
 @Dao
diff --git a/room/compiler/src/test/data/daoWriter/input/WriterDao.java b/room/compiler/src/test/data/daoWriter/input/WriterDao.java
index e122479..6a1ebee 100644
--- a/room/compiler/src/test/data/daoWriter/input/WriterDao.java
+++ b/room/compiler/src/test/data/daoWriter/input/WriterDao.java
@@ -15,7 +15,7 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 import java.util.List;
 
 @Dao
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index 7212da2..97878c6 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -1,13 +1,13 @@
 package foo.bar;
 
-import android.arch.lifecycle.ComputableLiveData;
-import android.arch.lifecycle.LiveData;
-import android.arch.persistence.room.InvalidationTracker.Observer;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.RoomSQLiteQuery;
-import android.arch.persistence.room.util.StringUtil;
 import android.database.Cursor;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ComputableLiveData;
+import androidx.lifecycle.LiveData;
+import androidx.room.InvalidationTracker.Observer;
+import androidx.room.RoomDatabase;
+import androidx.room.RoomSQLiteQuery;
+import androidx.room.util.StringUtil;
 import java.lang.Integer;
 import java.lang.Override;
 import java.lang.String;
@@ -18,7 +18,7 @@
 import java.util.Set;
 import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
+@Generated("androidx.room.RoomProcessor")
 @SuppressWarnings("unchecked")
 public class ComplexDao_Impl extends ComplexDao {
     private final RoomDatabase __db;
diff --git a/room/compiler/src/test/data/daoWriter/output/DeletionDao.java b/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
index 78ac94a..db9fb35 100644
--- a/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/DeletionDao.java
@@ -1,10 +1,10 @@
 package foo.bar;
 
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.SharedSQLiteStatement;
-import android.arch.persistence.room.util.StringUtil;
+import androidx.room.EntityDeletionOrUpdateAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.SharedSQLiteStatement;
+import androidx.room.util.StringUtil;
+import androidx.sqlite.db.SupportSQLiteStatement;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.StringBuilder;
@@ -12,7 +12,7 @@
 import java.util.List;
 import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
+@Generated("androidx.room.RoomProcessor")
 @SuppressWarnings("unchecked")
 public class DeletionDao_Impl implements DeletionDao {
   private final RoomDatabase __db;
diff --git a/room/compiler/src/test/data/daoWriter/output/UpdateDao.java b/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
index 8dc728f..7cab05a 100644
--- a/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/UpdateDao.java
@@ -1,16 +1,16 @@
 package foo.bar;
 
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.SharedSQLiteStatement;
+import androidx.room.EntityDeletionOrUpdateAdapter;
+import androidx.room.RoomDatabase;
+import androidx.room.SharedSQLiteStatement;
+import androidx.sqlite.db.SupportSQLiteStatement;
 import java.lang.Override;
 import java.lang.String;
 import java.lang.SuppressWarnings;
 import java.util.List;
 import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
+@Generated("androidx.room.RoomProcessor")
 @SuppressWarnings("unchecked")
 public class UpdateDao_Impl implements UpdateDao {
   private final RoomDatabase __db;
diff --git a/room/compiler/src/test/data/daoWriter/output/WriterDao.java b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
index 04cb0bf..829dad4 100644
--- a/room/compiler/src/test/data/daoWriter/output/WriterDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
@@ -16,9 +16,9 @@
 
 package foo.bar;
 
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.arch.persistence.room.EntityInsertionAdapter;
-import android.arch.persistence.room.RoomDatabase;
+import androidx.room.EntityInsertionAdapter;
+import androidx.room.RoomDatabase;
+import androidx.sqlite.db.SupportSQLiteStatement;
 
 import java.lang.Override;
 import java.lang.String;
@@ -26,7 +26,7 @@
 import java.util.List;
 import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
+@Generated("androidx.room.RoomProcessor")
 @SuppressWarnings("unchecked")
 public class WriterDao_Impl implements WriterDao {
     private final RoomDatabase __db;
diff --git a/room/compiler/src/test/data/databasewriter/input/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/input/ComplexDatabase.java
index 674e57e..537e7fe 100644
--- a/room/compiler/src/test/data/databasewriter/input/ComplexDatabase.java
+++ b/room/compiler/src/test/data/databasewriter/input/ComplexDatabase.java
@@ -15,7 +15,7 @@
  */
 
 package foo.bar;
-import android.arch.persistence.room.*;
+import androidx.room.*;
 import java.lang.SuppressWarnings;
 import java.util.List;
 
diff --git a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
index 17592a6..7f82dcd 100644
--- a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
+++ b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
@@ -1,17 +1,17 @@
 package foo.bar;
 
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
-import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
-import android.arch.persistence.room.DatabaseConfiguration;
-import android.arch.persistence.room.InvalidationTracker;
-import android.arch.persistence.room.RoomOpenHelper;
-import android.arch.persistence.room.RoomOpenHelper.Delegate;
-import android.arch.persistence.room.util.TableInfo;
-import android.arch.persistence.room.util.TableInfo.Column;
-import android.arch.persistence.room.util.TableInfo.ForeignKey;
-import android.arch.persistence.room.util.TableInfo.Index;
+import androidx.room.DatabaseConfiguration;
+import androidx.room.InvalidationTracker;
+import androidx.room.RoomOpenHelper;
+import androidx.room.RoomOpenHelper.Delegate;
+import androidx.room.util.TableInfo;
+import androidx.room.util.TableInfo.Column;
+import androidx.room.util.TableInfo.ForeignKey;
+import androidx.room.util.TableInfo.Index;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.SupportSQLiteOpenHelper.Callback;
+import androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration;
 import java.lang.IllegalStateException;
 import java.lang.Override;
 import java.lang.String;
@@ -20,7 +20,7 @@
 import java.util.HashSet;
 import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
+@Generated("androidx.room.RoomProcessor")
 @SuppressWarnings("unchecked")
 public class ComplexDatabase_Impl extends ComplexDatabase {
     private volatile ComplexDao _complexDao;
@@ -100,6 +100,8 @@
             super.setTransactionSuccessful();
         } finally {
             super.endTransaction();
+            _db.query("PRAGMA wal_checkpoint(FULL)").close();
+            _db.execSQL("VACUUM");
         }
     }
 
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/parser/SqlParserTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/parser/SqlParserTest.kt
deleted file mode 100644
index e6e8c3b..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/parser/SqlParserTest.kt
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.parser
-
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.not
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class SqlParserTest {
-
-    @Test
-    fun multipleQueries() {
-        assertErrors("SELECT * FROM users; SELECT * FROM books;",
-                ParserErrors.NOT_ONE_QUERY)
-    }
-
-    @Test
-    fun empty() {
-        assertErrors("", ParserErrors.NOT_ONE_QUERY)
-    }
-
-    @Test
-    fun deleteQuery() {
-        val parsed = SqlParser.parse("DELETE FROM users where id > 3")
-        assertThat(parsed.errors, `is`(emptyList()))
-        assertThat(parsed.type, `is`(QueryType.DELETE))
-    }
-
-    @Test
-    fun badDeleteQuery() {
-        assertErrors("delete from user where mAge >= :min && mAge <= :max",
-                "no viable alternative at input 'delete from user where mAge >= :min &&'")
-    }
-
-    @Test
-    fun updateQuery() {
-        val parsed = SqlParser.parse("UPDATE users set name = :name where id = :id")
-        assertThat(parsed.errors, `is`(emptyList()))
-        assertThat(parsed.type, `is`(QueryType.UPDATE))
-    }
-
-    @Test
-    fun explain() {
-        assertErrors("EXPLAIN QUERY PLAN SELECT * FROM users",
-                ParserErrors.invalidQueryType(QueryType.EXPLAIN))
-    }
-
-    @Test
-    fun validColumnNames() {
-        listOf("f", "fo", "f2", "f 2", "foo_2", "foo-2", "_", "foo bar baz",
-                "foo 2 baz", "_baz", "fooBar", "2", "*", "foo*2", "dsa$", "\$fsa",
-                "-bar", "ÅŸoöÄŸüı").forEach {
-            assertThat("name: $it", SqlParser.isValidIdentifier(it), `is`(true))
-        }
-    }
-
-    @Test
-    fun invalidColumnNames() {
-        listOf("", " ", "fd`a`", "f`a", "`a", "\"foo bar\"", "\"", "`").forEach {
-            assertThat("name: $it", SqlParser.isValidIdentifier(it), `is`(false))
-        }
-    }
-
-    @Test
-    fun extractTableNames() {
-        assertThat(SqlParser.parse("select * from users").tables,
-                `is`(setOf(Table("users", "users"))))
-        assertThat(SqlParser.parse("select * from users as ux").tables,
-                `is`(setOf(Table("users", "ux"))))
-        assertThat(SqlParser.parse("select * from (select * from books)").tables,
-                `is`(setOf(Table("books", "books"))))
-        assertThat(SqlParser.parse("select x.id from (select * from books) as x").tables,
-                `is`(setOf(Table("books", "books"))))
-    }
-
-    @Test
-    fun unescapeTableNames() {
-        assertThat(SqlParser.parse("select * from `users`").tables,
-                `is`(setOf(Table("users", "users"))))
-        assertThat(SqlParser.parse("select * from \"users\"").tables,
-                `is`(setOf(Table("users", "users"))))
-        assertThat(SqlParser.parse("select * from 'users'").tables,
-                `is`(setOf(Table("users", "users"))))
-    }
-
-    @Test
-    fun tablePrefixInInsert_set() {
-        // this is an invalid query, b/64539805
-        val query = SqlParser.parse("UPDATE trips SET trips.title=:title")
-        assertThat(query.errors, not(emptyList()))
-    }
-
-    @Test
-    fun tablePrefixInInsert_where() {
-        val query = SqlParser.parse("UPDATE trips SET title=:title WHERE trips.id=:id")
-        assertThat(query.errors, `is`(emptyList()))
-    }
-
-    @Test
-    fun tablePrefixInSelect_projection() {
-        val query = SqlParser.parse("SELECT a.name, b.last_name from user a, book b")
-        assertThat(query.errors, `is`(emptyList()))
-        assertThat(query.tables, `is`(setOf(Table("user", "a"),
-                Table("book", "b"))))
-    }
-
-    @Test
-    fun tablePrefixInSelect_where() {
-        val query = SqlParser.parse("SELECT a.name, b.last_name from user a, book b" +
-                " WHERE a.name = b.name")
-        assertThat(query.errors, `is`(emptyList()))
-        assertThat(query.tables, `is`(setOf(Table("user", "a"),
-                Table("book", "b"))))
-    }
-
-    @Test
-    fun findBindVariables() {
-        assertVariables("select * from users")
-        assertVariables("select * from users where name like ?", "?")
-        assertVariables("select * from users where name like :name", ":name")
-        assertVariables("select * from users where name like ?2", "?2")
-        assertVariables("select * from users where name like ?2 OR name LIKE ?1", "?2", "?1")
-        assertVariables("select * from users where name like @a", "@a")
-        assertVariables("select * from users where name like \$a", "\$a")
-    }
-
-    @Test
-    fun indexedVariablesError() {
-        assertErrors("select * from users where name like ?",
-                ParserErrors.ANONYMOUS_BIND_ARGUMENT)
-        assertErrors("select * from users where name like ? or last_name like ?",
-                ParserErrors.ANONYMOUS_BIND_ARGUMENT)
-        assertErrors("select * from users where name like ?1",
-                ParserErrors.cannotUseVariableIndices("?1", 36))
-    }
-
-    @Test
-    fun foo() {
-        assertSections("select * from users where name like ?",
-                Section.text("select * from users where name like "),
-                Section.bindVar("?"))
-
-        assertSections("select * from users where name like :name AND last_name like :lastName",
-                Section.text("select * from users where name like "),
-                Section.bindVar(":name"),
-                Section.text(" AND last_name like "),
-                Section.bindVar(":lastName"))
-
-        assertSections("select * from users where name \nlike :name AND last_name like :lastName",
-                Section.text("select * from users where name "),
-                Section.newline(),
-                Section.text("like "),
-                Section.bindVar(":name"),
-                Section.text(" AND last_name like "),
-                Section.bindVar(":lastName"))
-
-        assertSections("select * from users where name like :name \nAND last_name like :lastName",
-                Section.text("select * from users where name like "),
-                Section.bindVar(":name"),
-                Section.text(" "),
-                Section.newline(),
-                Section.text("AND last_name like "),
-                Section.bindVar(":lastName"))
-
-        assertSections("select * from users where name like :name \nAND last_name like \n:lastName",
-                Section.text("select * from users where name like "),
-                Section.bindVar(":name"),
-                Section.text(" "),
-                Section.newline(),
-                Section.text("AND last_name like "),
-                Section.newline(),
-                Section.bindVar(":lastName"))
-    }
-
-    fun assertVariables(query: String, vararg expected: String) {
-        assertThat((SqlParser.parse(query)).inputs.map { it.text }, `is`(expected.toList()))
-    }
-
-    fun assertErrors(query: String, vararg errors: String) {
-        assertThat((SqlParser.parse(query)).errors, `is`(errors.toList()))
-    }
-
-    fun assertSections(query: String, vararg sections: Section) {
-        assertThat(SqlParser.parse(query).sections, `is`(sections.toList()))
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseDaoTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseDaoTest.kt
deleted file mode 100644
index 19430af..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseDaoTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-package android.arch.persistence.room.processor
-
-import COMMON
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.vo.Dao
-import android.arch.persistence.room.writer.DaoWriter
-import com.google.auto.common.MoreTypes
-import com.google.testing.compile.JavaFileObjects
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import simpleRun
-
-/**
- * we don't assert much in these tests since if type resolution fails, compilation fails.
- */
-@RunWith(JUnit4::class)
-class BaseDaoTest {
-    private fun String.toJFO(qName: String) = JavaFileObjects.forSourceLines(qName, this)
-
-    @Test
-    fun insert() {
-        baseDao("""
-            @Insert
-            void insertMe(T t);
-        """) { dao ->
-            assertThat(dao.insertionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun insertArray() {
-        baseDao("""
-            @Insert
-            void insertMe(T[] t);
-        """) { dao ->
-            assertThat(dao.insertionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun insertVarArg() {
-        baseDao("""
-            @Insert
-            void insertMe(T... t);
-        """) { dao ->
-            assertThat(dao.insertionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun insertList() {
-        baseDao("""
-            @Insert
-            void insertMe(List<T> t);
-        """) { dao ->
-            assertThat(dao.insertionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun delete() {
-        baseDao("""
-            @Delete
-            void deleteMe(T t);
-        """) { dao ->
-            assertThat(dao.deletionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun deleteArray() {
-        baseDao("""
-            @Delete
-            void deleteMe(T[] t);
-        """) { dao ->
-            assertThat(dao.deletionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun deleteVarArg() {
-        baseDao("""
-            @Delete
-            void deleteMe(T... t);
-        """) { dao ->
-            assertThat(dao.deletionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun deleteList() {
-        baseDao("""
-            @Delete
-            void deleteMe(List<T> t);
-        """) { dao ->
-            assertThat(dao.deletionMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun update() {
-        baseDao("""
-            @Update
-            void updateMe(T t);
-        """) { dao ->
-            assertThat(dao.updateMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun updateArray() {
-        baseDao("""
-            @Update
-            void updateMe(T[] t);
-        """) { dao ->
-            assertThat(dao.updateMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun updateVarArg() {
-        baseDao("""
-            @Update
-            void updateMe(T... t);
-        """) { dao ->
-            assertThat(dao.updateMethods.size, `is`(1))
-        }
-    }
-
-    @Test
-    fun updateList() {
-        baseDao("""
-            @Update
-            void updateMe(List<T> t);
-        """) { dao ->
-            assertThat(dao.updateMethods.size, `is`(1))
-        }
-    }
-
-    fun baseDao(code: String, handler: (Dao) -> Unit) {
-        val baseClass = """
-            package foo.bar;
-            import android.arch.persistence.room.*;
-            import java.util.List;
-
-            interface BaseDao<K, T> {
-                $code
-            }
-        """.toJFO("foo.bar.BaseDao")
-        val extension = """
-            package foo.bar;
-            import android.arch.persistence.room.*;
-            @Dao
-            interface MyDao extends BaseDao<Integer, User> {
-            }
-        """.toJFO("foo.bar.MyDao")
-        simpleRun(baseClass, extension, COMMON.USER) { invocation ->
-            val daoElm = invocation.processingEnv.elementUtils.getTypeElement("foo.bar.MyDao")
-            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
-                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
-            val processedDao = DaoProcessor(invocation.context, daoElm, dbType, null).process()
-            handler(processedDao)
-            DaoWriter(processedDao, invocation.processingEnv).write(invocation.processingEnv)
-        }.compilesWithoutError()
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseEntityParserTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseEntityParserTest.kt
deleted file mode 100644
index bd09a11..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/BaseEntityParserTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Embedded
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.Entity
-import android.support.annotation.NonNull
-import com.google.auto.common.MoreElements
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import javax.tools.JavaFileObject
-
-abstract class BaseEntityParserTest {
-    companion object {
-        const val ENTITY_PREFIX = """
-            package foo.bar;
-            import android.arch.persistence.room.*;
-            import android.support.annotation.NonNull;
-            @Entity%s
-            public class MyEntity %s {
-            """
-        const val ENTITY_SUFFIX = "}"
-    }
-
-    fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
-                     baseClass: String = "",
-                     jfos: List<JavaFileObject> = emptyList(),
-                     handler: (Entity, TestInvocation) -> Unit): CompileTester {
-        val attributesReplacement: String
-        if (attributes.isEmpty()) {
-            attributesReplacement = ""
-        } else {
-            attributesReplacement = "(" +
-                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
-                    ")".trimIndent()
-        }
-        val baseClassReplacement: String
-        if (baseClass == "") {
-            baseClassReplacement = ""
-        } else {
-            baseClassReplacement = " extends $baseClass"
-        }
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(jfos + JavaFileObjects.forSourceString("foo.bar.MyEntity",
-                        ENTITY_PREFIX.format(attributesReplacement, baseClassReplacement)
-                                + input + ENTITY_SUFFIX
-                ))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(android.arch.persistence.room.Entity::class,
-                                android.arch.persistence.room.PrimaryKey::class,
-                                android.arch.persistence.room.Ignore::class,
-                                Embedded::class,
-                                android.arch.persistence.room.ColumnInfo::class,
-                                NonNull::class)
-                        .nextRunHandler { invocation ->
-                            val entity = invocation.roundEnv
-                                    .getElementsAnnotatedWith(
-                                            android.arch.persistence.room.Entity::class.java)
-                                    .first { it.toString() == "foo.bar.MyEntity" }
-                            val parser = EntityProcessor(invocation.context,
-                                    MoreElements.asType(entity))
-                            val parsedQuery = parser.process()
-                            handler(parsedQuery, invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/CustomConverterProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/CustomConverterProcessorTest.kt
deleted file mode 100644
index 067773a..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/CustomConverterProcessorTest.kt
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.TypeConverter
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
-import android.arch.persistence.room.processor.ProcessorErrors
-        .TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
-import android.arch.persistence.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.vo.CustomTypeConverter
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import com.squareup.javapoet.TypeVariableName
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import simpleRun
-import java.util.Date
-import javax.lang.model.element.Modifier
-import javax.tools.JavaFileObject
-
-@RunWith(JUnit4::class)
-class CustomConverterProcessorTest {
-    companion object {
-        val CONVERTER = ClassName.get("foo.bar", "MyConverter")!!
-        val CONVERTER_QNAME = CONVERTER.packageName() + "." + CONVERTER.simpleName()
-        val CONTAINER = JavaFileObjects.forSourceString("foo.bar.Container",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @TypeConverters(foo.bar.MyConverter.class)
-                public class Container {}
-                """)
-    }
-
-    @Test
-    fun validCase() {
-        singleClass(createConverter(TypeName.SHORT.box(), TypeName.CHAR.box())) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
-            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun primitiveFrom() {
-        singleClass(createConverter(TypeName.SHORT, TypeName.CHAR.box())) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
-            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun primitiveTo() {
-        singleClass(createConverter(TypeName.INT.box(), TypeName.DOUBLE)) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(TypeName.INT.box()))
-            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun primitiveBoth() {
-        singleClass(createConverter(TypeName.INT, TypeName.DOUBLE)) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(TypeName.INT))
-            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun nonNullButNotBoxed() {
-        val string = String::class.typeName()
-        val date = Date::class.typeName()
-        singleClass(createConverter(string, date)) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(string as TypeName))
-            assertThat(converter?.toTypeName, `is`(date as TypeName))
-        }
-    }
-
-    @Test
-    fun parametrizedTypeUnbound() {
-        val typeVarT = TypeVariableName.get("T")
-        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
-        val typeVarK = TypeVariableName.get("K")
-        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
-        singleClass(createConverter(list, map, listOf(typeVarK, typeVarT))) {
-            _, _ ->
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_UNBOUND_GENERIC)
-    }
-
-    @Test
-    fun parametrizedTypeSpecific() {
-        val string = String::class.typeName()
-        val date = Date::class.typeName()
-        val list = ParameterizedTypeName.get(List::class.typeName(), string)
-        val map = ParameterizedTypeName.get(Map::class.typeName(), string, date)
-        singleClass(createConverter(list, map)) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(list as TypeName))
-            assertThat(converter?.toTypeName, `is`(map as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testNoConverters() {
-        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
-                """
-                package ${CONVERTER.packageName()};
-                public class ${CONVERTER.simpleName()} {
-                }
-                """)) { _, _ ->
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
-    }
-
-    @Test
-    fun checkNoArgConstructor() {
-        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
-                """
-                package ${CONVERTER.packageName()};
-                import android.arch.persistence.room.TypeConverter;
-
-                public class ${CONVERTER.simpleName()} {
-                    public ${CONVERTER.simpleName()}(int x) {}
-                    @TypeConverter
-                    public int x(short y) {return 0;}
-                }
-                """)) { _, _ ->
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
-    }
-
-    @Test
-    fun checkNoArgConstructor_withStatic() {
-        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
-                """
-                package ${CONVERTER.packageName()};
-                import android.arch.persistence.room.TypeConverter;
-
-                public class ${CONVERTER.simpleName()} {
-                    public ${CONVERTER.simpleName()}(int x) {}
-                    @TypeConverter
-                    public static int x(short y) {return 0;}
-                }
-                """)) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
-            assertThat(converter?.toTypeName, `is`(TypeName.INT))
-            assertThat(converter?.isStatic, `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun checkPublic() {
-        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
-                """
-                package ${CONVERTER.packageName()};
-                import android.arch.persistence.room.TypeConverter;
-
-                public class ${CONVERTER.simpleName()} {
-                    @TypeConverter static int x(short y) {return 0;}
-                    @TypeConverter private static int y(boolean y) {return 0;}
-                }
-                """)) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
-            assertThat(converter?.toTypeName, `is`(TypeName.INT))
-            assertThat(converter?.isStatic, `is`(true))
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MUST_BE_PUBLIC).and()
-                .withErrorCount(2)
-    }
-
-    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-    @Test
-    fun parametrizedTypeBoundViaParent() {
-        val typeVarT = TypeVariableName.get("T")
-        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
-        val typeVarK = TypeVariableName.get("K")
-        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
-
-        val baseConverter = createConverter(list, map, listOf(typeVarT, typeVarK))
-        val extendingQName = "foo.bar.Extending"
-        val extendingClass = JavaFileObjects.forSourceString(extendingQName,
-                "package foo.bar;\n" +
-                        TypeSpec.classBuilder(ClassName.bestGuess(extendingQName)).apply {
-                            superclass(
-                                    ParameterizedTypeName.get(CONVERTER, String::class.typeName(),
-                                    Integer::class.typeName()))
-                        }.build().toString())
-
-        simpleRun(baseConverter, extendingClass) { invocation ->
-            val element = invocation.processingEnv.elementUtils.getTypeElement(extendingQName)
-            val converter = CustomConverterProcessor(invocation.context, element)
-                    .process().firstOrNull()
-            assertThat(converter?.fromTypeName, `is`(ParameterizedTypeName.get(
-                    List::class.typeName(), String::class.typeName()) as TypeName
-            ))
-            assertThat(converter?.toTypeName, `is`(ParameterizedTypeName.get(Map::class.typeName(),
-                    Integer::class.typeName(), String::class.typeName()) as TypeName
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun checkDuplicates() {
-        singleClass(
-                createConverter(TypeName.SHORT.box(), TypeName.CHAR.box(), duplicate = true)
-        ) { converter, _ ->
-            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
-            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
-        }.failsToCompile().withErrorContaining("Multiple methods define the same conversion")
-    }
-
-    private fun createConverter(
-            from: TypeName,
-            to: TypeName,
-            typeVariables: List<TypeVariableName> = emptyList(),
-            duplicate: Boolean = false
-    ): JavaFileObject {
-        val code = TypeSpec.classBuilder(CONVERTER).apply {
-            addTypeVariables(typeVariables)
-            addModifiers(Modifier.PUBLIC)
-            fun buildMethod(name: String) = MethodSpec.methodBuilder(name).apply {
-                addAnnotation(TypeConverter::class.java)
-                addModifiers(Modifier.PUBLIC)
-                returns(to)
-                addParameter(ParameterSpec.builder(from, "input").build())
-                if (to.isPrimitive) {
-                    addStatement("return 0")
-                } else {
-                    addStatement("return null")
-                }
-            }.build()
-            addMethod(buildMethod("convertF"))
-            if (duplicate) {
-                addMethod(buildMethod("convertF2"))
-            }
-        }.build().toString()
-        return JavaFileObjects.forSourceString(CONVERTER.toString(),
-                "package ${CONVERTER.packageName()};\n$code")
-    }
-
-    private fun singleClass(
-            vararg jfo: JavaFileObject,
-            handler: (CustomTypeConverter?, TestInvocation) -> Unit
-    ): CompileTester {
-        return simpleRun(*((jfo.toList() + CONTAINER).toTypedArray())) { invocation ->
-            val processed = CustomConverterProcessor.findConverters(invocation.context,
-                    invocation.processingEnv.elementUtils.getTypeElement("foo.bar.Container"))
-            handler(processed.converters.firstOrNull()?.custom, invocation)
-        }
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt
deleted file mode 100644
index aa717f8..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DaoProcessorTest.kt
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import COMMON
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.Dao
-import android.arch.persistence.room.vo.Warning
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import createVerifierFromEntities
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-class DaoProcessorTest(val enableVerification: Boolean) {
-    companion object {
-        const val DAO_PREFIX = """
-            package foo.bar;
-            import android.arch.persistence.room.*;
-            """
-        @Parameterized.Parameters(name = "enableDbVerification={0}")
-        @JvmStatic
-        fun getParams() = arrayOf(true, false)
-    }
-
-    @Test
-    fun testNonAbstract() {
-        singleDao("@Dao public class MyDao {}") { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
-    }
-
-    @Test
-    fun testAbstractMethodWithoutQuery() {
-        singleDao("""
-                @Dao public interface MyDao {
-                    int getFoo();
-                }
-        """) { _, _ ->
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION)
-    }
-
-    @Test
-    fun testBothAnnotations() {
-        singleDao("""
-                @Dao public interface MyDao {
-                    @Query("select 1")
-                    @Insert
-                    int getFoo(int x);
-                }
-        """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD)
-    }
-
-    @Test
-    fun testAbstractClass() {
-        singleDao("""
-                @Dao abstract class MyDao {
-                    @Query("SELECT uid FROM User")
-                    abstract int[] getIds();
-                }
-                """) { dao, _ ->
-            assertThat(dao.queryMethods.size, `is`(1))
-            val method = dao.queryMethods.first()
-            assertThat(method.name, `is`("getIds"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testInterface() {
-        singleDao("""
-                @Dao interface MyDao {
-                    @Query("SELECT uid FROM User")
-                    abstract int[] getIds();
-                }
-                """) { dao, _ ->
-            assertThat(dao.queryMethods.size, `is`(1))
-            val method = dao.queryMethods.first()
-            assertThat(method.name, `is`("getIds"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testWithInsertAndQuery() {
-        singleDao("""
-                @Dao abstract class MyDao {
-                    @Query("SELECT uid FROM User")
-                    abstract int[] getIds();
-                    @Insert
-                    abstract void insert(User user);
-                }
-                """) { dao, _ ->
-            assertThat(dao.queryMethods.size, `is`(1))
-            val method = dao.queryMethods.first()
-            assertThat(method.name, `is`("getIds"))
-            assertThat(dao.insertionMethods.size, `is`(1))
-            val insertMethod = dao.insertionMethods.first()
-            assertThat(insertMethod.name, `is`("insert"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun skipQueryVerification() {
-        singleDao("""
-                @Dao @SkipQueryVerification interface MyDao {
-                    @Query("SELECT nonExistingField FROM User")
-                    abstract int[] getIds();
-                }
-                """) { dao, _ ->
-            assertThat(dao.queryMethods.size, `is`(1))
-            val method = dao.queryMethods.first()
-            assertThat(method.name, `is`("getIds"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun suppressedWarnings() {
-        singleDao("""
-            @SuppressWarnings({"ALL", RoomWarnings.CURSOR_MISMATCH})
-            @Dao interface MyDao {
-                @Query("SELECT * from user")
-                abstract User users();
-            }
-            """) { dao, invocation ->
-            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
-                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
-            val daoProcessor = DaoProcessor(invocation.context, dao.element, dbType, null)
-            assertThat(daoProcessor.context.logger
-                    .suppressedWarnings, `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
-
-            dao.queryMethods.forEach {
-                assertThat(QueryMethodProcessor(
-                        baseContext = daoProcessor.context,
-                        containing = MoreTypes.asDeclared(dao.element.asType()),
-                        executableElement = it.element,
-                        dbVerifier = null).context.logger.suppressedWarnings,
-                        `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
-            }
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun suppressedWarningsInheritance() {
-        singleDao("""
-            @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
-            @Dao interface MyDao {
-                @SuppressWarnings("ALL")
-                @Query("SELECT * from user")
-                abstract User users();
-            }
-            """) { dao, invocation ->
-            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
-                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
-            val daoProcessor = DaoProcessor(invocation.context, dao.element, dbType, null)
-            assertThat(daoProcessor.context.logger
-                    .suppressedWarnings, `is`(setOf(Warning.CURSOR_MISMATCH)))
-
-            dao.queryMethods.forEach {
-                assertThat(QueryMethodProcessor(
-                        baseContext = daoProcessor.context,
-                        containing = MoreTypes.asDeclared(dao.element.asType()),
-                        executableElement = it.element,
-                        dbVerifier = null).context.logger.suppressedWarnings,
-                        `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
-            }
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun query_warnIfTransactionIsMissingForRelation() {
-        if (!enableVerification) {
-            return
-        }
-        singleDao(
-                """
-                @Dao interface MyDao {
-                    static class Merged extends User {
-                       @Relation(parentColumn = "name", entityColumn = "lastName",
-                                 entity = User.class)
-                       java.util.List<User> users;
-                    }
-                    @Query("select * from user")
-                    abstract java.util.List<Merged> loadUsers();
-                }
-                """
-        ) { dao, _ ->
-            assertThat(dao.queryMethods.size, `is`(1))
-            assertThat(dao.queryMethods.first().inTransaction, `is`(false))
-        }.compilesWithoutError()
-                .withWarningContaining(ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
-    }
-
-    @Test
-    fun query_dontWarnIfTransactionIsMissingForRelation_suppressed() {
-        if (!enableVerification) {
-            return
-        }
-        singleDao(
-                """
-                @Dao interface MyDao {
-                    static class Merged extends User {
-                       @Relation(parentColumn = "name", entityColumn = "lastName",
-                                 entity = User.class)
-                       java.util.List<User> users;
-                    }
-                    @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
-                    @Query("select * from user")
-                    abstract java.util.List<Merged> loadUsers();
-                }
-                """
-        ) { dao, _ ->
-            assertThat(dao.queryMethods.size, `is`(1))
-            assertThat(dao.queryMethods.first().inTransaction, `is`(false))
-        }.compilesWithoutError()
-                .withWarningCount(0)
-    }
-
-    @Test
-    fun query_dontWarnIfTransactionNotIsMissingForRelation() {
-        if (!enableVerification) {
-            return
-        }
-        singleDao(
-                """
-                @Dao interface MyDao {
-                    static class Merged extends User {
-                       @Relation(parentColumn = "name", entityColumn = "lastName",
-                                 entity = User.class)
-                       java.util.List<User> users;
-                    }
-                    @Transaction
-                    @Query("select * from user")
-                    abstract java.util.List<Merged> loadUsers();
-                }
-                """
-        ) { dao, _ ->
-            // test sanity
-            assertThat(dao.queryMethods.size, `is`(1))
-            assertThat(dao.queryMethods.first().inTransaction, `is`(true))
-        }.compilesWithoutError()
-                .withWarningCount(0)
-    }
-
-    fun singleDao(vararg inputs: String, handler: (Dao, TestInvocation) -> Unit):
-            CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyDao",
-                        DAO_PREFIX + inputs.joinToString("\n")
-                ), COMMON.USER))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(android.arch.persistence.room.Dao::class,
-                                android.arch.persistence.room.Entity::class,
-                                android.arch.persistence.room.Relation::class,
-                                android.arch.persistence.room.Transaction::class,
-                                android.arch.persistence.room.ColumnInfo::class,
-                                android.arch.persistence.room.PrimaryKey::class,
-                                android.arch.persistence.room.Query::class)
-                        .nextRunHandler { invocation ->
-                            val dao = invocation.roundEnv
-                                    .getElementsAnnotatedWith(
-                                            android.arch.persistence.room.Dao::class.java)
-                                    .first()
-                            val dbVerifier = if (enableVerification) {
-                                createVerifierFromEntities(invocation)
-                            } else {
-                                null
-                            }
-                            val dbType = MoreTypes.asDeclared(
-                                    invocation.context.processingEnv.elementUtils
-                                            .getTypeElement(RoomTypeNames.ROOM_DB.toString())
-                                            .asType())
-                            val parser = DaoProcessor(invocation.context,
-                                    MoreElements.asType(dao), dbType, dbVerifier)
-
-                            val parsedDao = parser.process()
-                            handler(parsedDao, invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DatabaseProcessorTest.kt
deleted file mode 100644
index d192c8f..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DatabaseProcessorTest.kt
+++ /dev/null
@@ -1,711 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.RoomProcessor
-import android.arch.persistence.room.solver.query.result.EntityRowAdapter
-import android.arch.persistence.room.solver.query.result.PojoRowAdapter
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.Database
-import android.arch.persistence.room.vo.Warning
-import com.google.auto.common.MoreElements
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.ClassName
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.instanceOf
-import org.hamcrest.CoreMatchers.not
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.CoreMatchers.sameInstance
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.tools.JavaFileObject
-import javax.tools.StandardLocation
-
-@RunWith(JUnit4::class)
-class DatabaseProcessorTest {
-    companion object {
-        const val DATABASE_PREFIX = """
-            package foo.bar;
-            import android.arch.persistence.room.*;
-            """
-        val DB1: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db1",
-                """
-                $DATABASE_PREFIX
-                @Database(entities = {Book.class}, version = 42)
-                public abstract class Db1 extends RoomDatabase {
-                    abstract BookDao bookDao();
-                }
-                """)
-        val DB2: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db2",
-                """
-                $DATABASE_PREFIX
-                @Database(entities = {Book.class}, version = 42)
-                public abstract class Db2 extends RoomDatabase {
-                    abstract BookDao bookDao();
-                }
-                """)
-        val DB3: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db3",
-                """
-                $DATABASE_PREFIX
-                @Database(entities = {Book.class}, version = 42)
-                public abstract class Db3 extends RoomDatabase {
-                }
-                """)
-        val USER: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.User",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity
-                public class User {
-                    @PrimaryKey
-                    int uid;
-                }
-                """)
-        val USER_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.UserDao",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Dao
-                public interface UserDao {
-                    @Query("SELECT * FROM user")
-                    public java.util.List<User> loadAll();
-
-                    @Insert
-                    public void insert(User... users);
-
-                    @Query("SELECT * FROM user where uid = :uid")
-                    public User loadOne(int uid);
-
-                    @TypeConverters(Converter.class)
-                    @Query("SELECT * FROM user where uid = :uid")
-                    public User loadWithConverter(int uid);
-
-                    @Query("SELECT * FROM user where uid = :uid")
-                    public Pojo loadOnePojo(int uid);
-
-                    @Query("SELECT * FROM user")
-                    public java.util.List<Pojo> loadAllPojos();
-
-                    @TypeConverters(Converter.class)
-                    @Query("SELECT * FROM user where uid = :uid")
-                    public Pojo loadPojoWithConverter(int uid);
-
-                    public static class Converter {
-                        @TypeConverter
-                        public static java.util.Date foo(Long input) {return null;}
-                    }
-
-                    public static class Pojo {
-                        public int uid;
-                    }
-                }
-                """)
-        val BOOK: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Book",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity
-                public class Book {
-                    @PrimaryKey
-                    int bookId;
-                }
-                """)
-        val BOOK_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.BookDao",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Dao
-                public interface BookDao {
-                    @Query("SELECT * FROM book")
-                    public java.util.List<Book> loadAllBooks();
-                    @Insert
-                    public void insert(Book book);
-                }
-                """)
-    }
-
-    @Test
-    fun simple() {
-        singleDb("""
-            @Database(entities = {User.class}, version = 42)
-            public abstract class MyDb extends RoomDatabase {
-                abstract UserDao userDao();
-            }
-            """, USER, USER_DAO) { db, _ ->
-            assertThat(db.daoMethods.size, `is`(1))
-            assertThat(db.entities.size, `is`(1))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun multiple() {
-        singleDb("""
-            @Database(entities = {User.class, Book.class}, version = 42)
-            public abstract class MyDb extends RoomDatabase {
-                abstract UserDao userDao();
-                abstract BookDao bookDao();
-            }
-            """, USER, USER_DAO, BOOK, BOOK_DAO) { db, _ ->
-            assertThat(db.daoMethods.size, `is`(2))
-            assertThat(db.entities.size, `is`(2))
-            assertThat(db.daoMethods.map { it.name }, `is`(listOf("userDao", "bookDao")))
-            assertThat(db.entities.map { it.type.toString() },
-                    `is`(listOf("foo.bar.User", "foo.bar.Book")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun detectMissingBaseClass() {
-        singleDb("""
-            @Database(entities = {User.class, Book.class}, version = 42)
-            public abstract class MyDb {
-            }
-            """, USER, BOOK) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
-    }
-
-    @Test
-    fun detectMissingTable() {
-        singleDb(
-                """
-                @Database(entities = {Book.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                    abstract BookDao bookDao();
-                }
-                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Dao
-                public interface BookDao {
-                    @Query("SELECT * FROM nonExistentTable")
-                    public java.util.List<Book> loadAllBooks();
-                }
-                """)) { _, _ ->
-        }.failsToCompile().withErrorContaining("no such table: nonExistentTable")
-    }
-
-    @Test
-    fun detectDuplicateTableNames() {
-        singleDb("""
-                @Database(entities = {User.class, AnotherClass.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                    abstract UserDao userDao();
-                }
-                """, USER, USER_DAO, JavaFileObjects.forSourceString("foo.bar.AnotherClass",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(tableName="user")
-                public class AnotherClass {
-                    @PrimaryKey
-                    int uid;
-                }
-                """)) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.duplicateTableNames("user",
-                        listOf("foo.bar.User", "foo.bar.AnotherClass"))
-        )
-    }
-
-    @Test
-    fun skipBadQueryVerification() {
-        singleDb(
-                """
-                @SkipQueryVerification
-                @Database(entities = {Book.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                    abstract BookDao bookDao();
-                }
-                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Dao
-                public interface BookDao {
-                    @Query("SELECT nonExistingField FROM Book")
-                    public java.util.List<Book> loadAllBooks();
-                }
-                """)) { _, _ ->
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun multipleDatabases() {
-        val db1_2 = JavaFileObjects.forSourceString("foo.barx.Db1",
-                """
-                package foo.barx;
-                import android.arch.persistence.room.*;
-                import foo.bar.*;
-                @Database(entities = {Book.class}, version = 42)
-                public abstract class Db1 extends RoomDatabase {
-                    abstract BookDao bookDao();
-                }
-                """)
-        Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2))
-                .processedWith(RoomProcessor())
-                .compilesWithoutError()
-                .and()
-                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db1_Impl.class")
-                .and()
-                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db2_Impl.class")
-                .and()
-                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.barx", "Db1_Impl.class")
-                .and()
-                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
-                        "BookDao_Db1_0_Impl.class")
-                .and()
-                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
-                        "BookDao_Db1_1_Impl.class")
-                .and()
-                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
-                        "BookDao_Db2_Impl.class")
-    }
-
-    @Test
-    fun twoDaoMethodsForTheSameDao() {
-        singleDb(
-                """
-                @Database(entities = {User.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                    abstract UserDao userDao();
-                    abstract UserDao userDao2();
-                }
-                """, USER, USER_DAO) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
-                .and()
-                .withErrorContaining(ProcessorErrors.duplicateDao(
-                        ClassName.get("foo.bar", "UserDao"), listOf("userDao", "userDao2")
-                ))
-    }
-
-    @Test
-    fun suppressedWarnings() {
-        singleDb(
-                """
-                @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
-                @Database(entities = {User.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                    abstract UserDao userDao();
-                }
-                """, USER, USER_DAO) { db, invocation ->
-            assertThat(DatabaseProcessor(invocation.context, db.element)
-                    .context.logger.suppressedWarnings, `is`(setOf(Warning.CURSOR_MISMATCH)))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun duplicateIndexNames() {
-        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(indices = {@Index(name ="index_name", value = {"name"})})
-                public class Entity1 {
-                    @PrimaryKey
-                    int uid;
-                    String name;
-                }
-                """)
-
-        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(indices = {@Index(name ="index_name", value = {"anotherName"})})
-                public class Entity2 {
-                    @PrimaryKey
-                    int uid;
-                    String anotherName;
-                }
-                """)
-        singleDb("""
-                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                }
-                """, entity1, entity2) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.duplicateIndexInDatabase("index_name",
-                        listOf("foo.bar.Entity1 > index_name", "foo.bar.Entity2 > index_name"))
-        )
-    }
-
-    @Test
-    fun foreignKey_missingParent() {
-        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
-                        parentColumns = "lastName",
-                        childColumns = "name"))
-                public class Entity1 {
-                    @PrimaryKey
-                    int uid;
-                    String name;
-                }
-                """)
-        singleDb("""
-                @Database(entities = {Entity1.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                }
-                """, entity1, COMMON.USER) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.foreignKeyMissingParentEntityInDatabase("User", "foo.bar.Entity1")
-        )
-    }
-
-    @Test
-    fun foreignKey_missingParentIndex() {
-        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
-                        parentColumns = "lastName",
-                        childColumns = "name"))
-                public class Entity1 {
-                    @PrimaryKey
-                    int uid;
-                    String name;
-                }
-                """)
-        singleDb("""
-                @Database(entities = {Entity1.class, User.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                }
-                """, entity1, COMMON.USER) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.foreignKeyMissingIndexInParent(
-                        parentEntity = COMMON.USER_TYPE_NAME.toString(),
-                        parentColumns = listOf("lastName"),
-                        childEntity = "foo.bar.Entity1",
-                        childColumns = listOf("name")
-                )
-        )
-    }
-
-    @Test
-    fun foreignKey_goodWithPrimaryKey() {
-        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
-                    parentColumns = "uid",
-                    childColumns = "parentId"))
-                public class Entity1 {
-                    @PrimaryKey
-                    int uid;
-                    int parentId;
-                    String name;
-                }
-                """)
-
-        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity
-                public class Entity2 {
-                    @PrimaryKey
-                    int uid;
-                    String anotherName;
-                }
-                """)
-        singleDb("""
-                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                }
-                """, entity1, entity2) { _, _ ->
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun foreignKey_missingParentColumn() {
-        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
-                    parentColumns = {"anotherName", "anotherName2"},
-                    childColumns = {"name", "name2"}))
-                public class Entity1 {
-                    @PrimaryKey
-                    int uid;
-                    String name;
-                    String name2;
-                }
-                """)
-
-        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity
-                public class Entity2 {
-                    @PrimaryKey
-                    int uid;
-                    String anotherName;
-                }
-                """)
-        singleDb("""
-                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                }
-                """, entity1, entity2) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.foreignKeyParentColumnDoesNotExist("foo.bar.Entity2",
-                        "anotherName2", listOf("uid", "anotherName"))
-        )
-    }
-
-    @Test
-    fun foreignKey_goodWithIndex() {
-        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
-                    parentColumns = {"anotherName", "anotherName2"},
-                    childColumns = {"name", "name2"}))
-                public class Entity1 {
-                    @PrimaryKey
-                    int uid;
-                    String name;
-                    String name2;
-                }
-                """)
-
-        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(indices = @Index(value = {"anotherName2", "anotherName"}, unique = true))
-                public class Entity2 {
-                    @PrimaryKey
-                    int uid;
-                    String anotherName;
-                    String anotherName2;
-                }
-                """)
-        singleDb("""
-                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                }
-                """, entity1, entity2) { _, _ ->
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertNotAReferencedEntity() {
-        singleDb("""
-                @Database(entities = {User.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                    abstract BookDao bookDao();
-                }
-                """, USER, USER_DAO, BOOK, BOOK_DAO) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.shortcutEntityIsNotInDatabase(
-                        database = "foo.bar.MyDb",
-                        dao = "foo.bar.BookDao",
-                        entity = "foo.bar.Book"
-                )
-        )
-    }
-
-    @Test
-    fun cache_entity() {
-        singleDb("""
-                @Database(entities = {User.class}, version = 42)
-                @SkipQueryVerification
-                public abstract class MyDb extends RoomDatabase {
-                    public abstract MyUserDao userDao();
-                    @Dao
-                    interface MyUserDao {
-                        @Insert
-                        public void insert(User... users);
-
-                        @Query("SELECT * FROM user where uid = :uid")
-                        public User loadOne(int uid);
-
-                        @TypeConverters(Converter.class)
-                        @Query("SELECT * FROM user where uid = :uid")
-                        public User loadWithConverter(int uid);
-                    }
-                    public static class Converter {
-                        @TypeConverter
-                        public static java.util.Date foo(Long input) {return null;}
-                    }
-                }
-                """, USER, USER_DAO) { db, _ ->
-            val userDao = db.daoMethods.first().dao
-            val insertionMethod = userDao.insertionMethods.find { it.name == "insert" }
-            assertThat(insertionMethod, notNullValue())
-            val loadOne = userDao.queryMethods.find { it.name == "loadOne" }
-            assertThat(loadOne, notNullValue())
-            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
-            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
-            val adapterEntity = (adapter as EntityRowAdapter).entity
-            assertThat(insertionMethod?.entities?.values?.first(), sameInstance(adapterEntity))
-
-            val withConverter = userDao.queryMethods.find { it.name == "loadWithConverter" }
-            assertThat(withConverter, notNullValue())
-            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
-            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
-            val convAdapterEntity = (convAdapter as EntityRowAdapter).entity
-            assertThat(insertionMethod?.entities?.values?.first(),
-                    not(sameInstance(convAdapterEntity)))
-
-            assertThat(convAdapterEntity, notNullValue())
-            assertThat(adapterEntity, notNullValue())
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun cache_pojo() {
-        singleDb("""
-                @Database(entities = {User.class}, version = 42)
-                public abstract class MyDb extends RoomDatabase {
-                    public abstract UserDao userDao();
-                }
-                """, USER, USER_DAO) { db, _ ->
-            val userDao = db.daoMethods.first().dao
-            val loadOne = userDao.queryMethods.find { it.name == "loadOnePojo" }
-            assertThat(loadOne, notNullValue())
-            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
-            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
-            val adapterPojo = (adapter as PojoRowAdapter).pojo
-
-            val loadAll = userDao.queryMethods.find { it.name == "loadAllPojos" }
-            assertThat(loadAll, notNullValue())
-            val loadAllAdapter = loadAll?.queryResultBinder?.adapter?.rowAdapter
-            assertThat("test sanity", loadAllAdapter, instanceOf(PojoRowAdapter::class.java))
-            val loadAllPojo = (loadAllAdapter as PojoRowAdapter).pojo
-            assertThat(adapter, not(sameInstance(loadAllAdapter)))
-            assertThat(adapterPojo, sameInstance(loadAllPojo))
-
-            val withConverter = userDao.queryMethods.find { it.name == "loadPojoWithConverter" }
-            assertThat(withConverter, notNullValue())
-            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
-            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
-            val convAdapterPojo = (convAdapter as PojoRowAdapter).pojo
-            assertThat(convAdapterPojo, notNullValue())
-            assertThat(convAdapterPojo, not(sameInstance(adapterPojo)))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun daoConstructor_RoomDatabase() {
-        assertConstructor(listOf(DB1), "BookDao(RoomDatabase db) {}")
-                .compilesWithoutError()
-    }
-
-    @Test
-    fun daoConstructor_specificDatabase() {
-        assertConstructor(listOf(DB1), "BookDao(Db1 db) {}")
-                .compilesWithoutError()
-    }
-
-    @Test
-    fun daoConstructor_wrongDatabase() {
-        assertConstructor(listOf(DB1, DB3), "BookDao(Db3 db) {}")
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors
-                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db1"))
-    }
-
-    @Test
-    fun daoConstructor_multipleDatabases_RoomDatabase() {
-        assertConstructor(listOf(DB1, DB2), "BookDao(RoomDatabase db) {}")
-                .compilesWithoutError()
-    }
-
-    @Test
-    fun daoConstructor_multipleDatabases_specificDatabases() {
-        assertConstructor(listOf(DB1, DB2), """
-                    BookDao(Db1 db) {}
-                    BookDao(Db2 db) {}
-                """)
-                .compilesWithoutError()
-    }
-
-    @Test
-    fun daoConstructor_multipleDatabases_empty() {
-        assertConstructor(listOf(DB1, DB2), """
-                    BookDao(Db1 db) {}
-                    BookDao() {} // Db2 uses this
-                """)
-                .compilesWithoutError()
-    }
-
-    @Test
-    fun daoConstructor_multipleDatabases_noMatch() {
-        assertConstructor(listOf(DB1, DB2), """
-                    BookDao(Db1 db) {}
-                """)
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors
-                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db2"))
-    }
-
-    fun assertConstructor(dbs: List<JavaFileObject>, constructor: String): CompileTester {
-        val bookDao = JavaFileObjects.forSourceString("foo.bar.BookDao",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Dao
-                public abstract class BookDao {
-                    $constructor
-                }
-                """)
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(BOOK, bookDao) + dbs)
-                .processedWith(RoomProcessor())
-    }
-
-    fun singleDb(input: String, vararg otherFiles: JavaFileObject,
-                 handler: (Database, TestInvocation) -> Unit): CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(otherFiles.toMutableList()
-                        + JavaFileObjects.forSourceString("foo.bar.MyDb",
-                        DATABASE_PREFIX + input
-                ))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(android.arch.persistence.room.Database::class)
-                        .nextRunHandler { invocation ->
-                            val entity = invocation.roundEnv
-                                    .getElementsAnnotatedWith(
-                                            android.arch.persistence.room.Database::class.java)
-                                    .first()
-                            val parser = DatabaseProcessor(invocation.context,
-                                    MoreElements.asType(entity))
-                            val parsedDb = parser.process()
-                            handler(parsedDb, invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessorTest.kt
deleted file mode 100644
index 40546e8..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/DeletionMethodProcessorTest.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Delete
-import android.arch.persistence.room.processor.ProcessorErrors.DELETION_MISSING_PARAMS
-import android.arch.persistence.room.processor.ProcessorErrors
-        .DELETION_METHODS_MUST_RETURN_VOID_OR_INT
-import android.arch.persistence.room.vo.DeletionMethod
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-@RunWith(JUnit4::class)
-class DeletionMethodProcessorTest : ShortcutMethodProcessorTest<DeletionMethod>(Delete::class) {
-    override fun invalidReturnTypeError(): String = DELETION_METHODS_MUST_RETURN_VOID_OR_INT
-
-    override fun noParamsError(): String = DELETION_MISSING_PARAMS
-
-    override fun process(baseContext: Context, containing: DeclaredType,
-                         executableElement: ExecutableElement): DeletionMethod {
-        return DeletionMethodProcessor(baseContext, containing, executableElement).process()
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityNameMatchingVariationsTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityNameMatchingVariationsTest.kt
deleted file mode 100644
index 00dde74..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityNameMatchingVariationsTest.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.vo.CallType
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.FieldGetter
-import android.arch.persistence.room.vo.FieldSetter
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import javax.lang.model.type.TypeKind.INT
-
-@RunWith(Parameterized::class)
-class EntityNameMatchingVariationsTest(triple: Triple<String, String, String>) :
-        BaseEntityParserTest() {
-    val fieldName = triple.first
-    val getterName = triple.second
-    val setterName = triple.third
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun params(): List<Triple<String, String, String>> {
-            val result = arrayListOf<Triple<String, String, String>>()
-            arrayListOf("x", "_x", "mX").forEach { field ->
-                arrayListOf("getX", "x").forEach { getter ->
-                    arrayListOf("setX", "x").forEach { setter ->
-                        result.add(Triple(field, getter, setter))
-                    }
-                }
-            }
-            return result
-        }
-    }
-
-    @Test
-    fun testSuccessfulParamToMethodMatching() {
-        singleEntity("""
-                @PrimaryKey
-                private int $fieldName;
-                public int $getterName() { return $fieldName; }
-                public void $setterName(int id) { this.$fieldName = id; }
-            """) { entity, invocation ->
-            assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
-            assertThat(entity.fields.size, `is`(1))
-            val field = entity.fields.first()
-            val intType = invocation.processingEnv.typeUtils.getPrimitiveType(INT)
-            assertThat(field, `is`(Field(
-                    element = field.element,
-                    name = fieldName,
-                    type = intType,
-                    columnName = fieldName,
-                    affinity = SQLTypeAffinity.INTEGER)))
-            assertThat(field.setter, `is`(FieldSetter(setterName, intType, CallType.METHOD)))
-            assertThat(field.getter, `is`(FieldGetter(getterName, intType, CallType.METHOD)))
-        }.compilesWithoutError()
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt
deleted file mode 100644
index 95c70ca..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/EntityProcessorTest.kt
+++ /dev/null
@@ -1,1907 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import COMMON
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.processor.ProcessorErrors.RELATION_IN_ENTITY
-import android.arch.persistence.room.vo.CallType
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.FieldGetter
-import android.arch.persistence.room.vo.FieldSetter
-import android.arch.persistence.room.vo.Index
-import android.arch.persistence.room.vo.Pojo
-import com.google.testing.compile.JavaFileObjects
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.lang.model.type.TypeKind.INT
-
-@RunWith(JUnit4::class)
-class EntityProcessorTest : BaseEntityParserTest() {
-    @Test
-    fun simple() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public int getId() { return id; }
-                public void setId(int id) { this.id = id; }
-            """) { entity, invocation ->
-            assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
-            assertThat(entity.fields.size, `is`(1))
-            val field = entity.fields.first()
-            val intType = invocation.processingEnv.typeUtils.getPrimitiveType(INT)
-            assertThat(field, `is`(Field(
-                    element = field.element,
-                    name = "id",
-                    type = intType,
-                    columnName = "id",
-                    affinity = SQLTypeAffinity.INTEGER)))
-            assertThat(field.setter, `is`(FieldSetter("setId", intType, CallType.METHOD)))
-            assertThat(field.getter, `is`(FieldGetter("getId", intType, CallType.METHOD)))
-            assertThat(entity.primaryKey.fields, `is`(listOf(field)))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun noGetter() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public void setId(int id) {this.id = id;}
-                """) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
-    }
-
-    @Test
-    fun getterWithBadType() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public float getId() {return 0f;}
-                public void setId(int id) {this.id = id;}
-                """) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
-    }
-
-    @Test
-    fun setterWithBadType() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public int getId() {return id;}
-                public void setId(float id) {}
-                """) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
-    }
-
-    @Test
-    fun setterWithAssignableType() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public int getId() {return id;}
-                public void setId(Integer id) {}
-                """) { _, _ -> }
-                .compilesWithoutError()
-    }
-
-    @Test
-    fun getterWithAssignableType() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public Integer getId() {return id;}
-                public void setId(int id) {}
-                """) { _, _ -> }
-                .compilesWithoutError()
-    }
-
-    @Test
-    fun noSetter() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public int getId(){ return id; }
-                """) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
-    }
-
-    @Test
-    fun tooManyGetters() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public void setId(int id) {}
-                public int getId(){ return id; }
-                public int id(){ return id; }
-                """) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining("getId, id")
-    }
-
-    @Test
-    fun tooManyGettersWithIgnore() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public void setId(int id) {}
-                public int getId(){ return id; }
-                @Ignore public int id(){ return id; }
-                """) { entity, _ ->
-            assertThat(entity.fields.first().getter.name, `is`("getId"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun tooManyGettersWithDifferentVisibility() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public void setId(int id) {}
-                public int getId(){ return id; }
-                protected int id(){ return id; }
-                """) { entity, _ ->
-            assertThat(entity.fields.first().getter.name, `is`("getId"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun tooManyGettersWithDifferentTypes() {
-        singleEntity("""
-                @PrimaryKey
-                public int id;
-                public void setId(int id) {}
-                public int getId(){ return id; }
-                """) { entity, _ ->
-            assertThat(entity.fields.first().getter.name, `is`("id"))
-            assertThat(entity.fields.first().getter.callType, `is`(CallType.FIELD))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun tooManySetters() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public void setId(int id) {}
-                public void id(int id) {}
-                public int getId(){ return id; }
-                """) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining("setId, id")
-    }
-
-    @Test
-    fun tooManySettersWithIgnore() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public void setId(int id) {}
-                @Ignore public void id(int id) {}
-                public int getId(){ return id; }
-                """) { entity, _ ->
-            assertThat(entity.fields.first().setter.name, `is`("setId"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun tooManySettersWithDifferentVisibility() {
-        singleEntity("""
-                @PrimaryKey
-                private int id;
-                public void setId(int id) {}
-                protected void id(int id) {}
-                public int getId(){ return id; }
-                """) { entity, _ ->
-            assertThat(entity.fields.first().setter.name, `is`("setId"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun tooManySettersWithDifferentTypes() {
-        singleEntity("""
-                @PrimaryKey
-                public int id;
-                public void setId(int id) {}
-                public int getId(){ return id; }
-                """) { entity, _ ->
-            assertThat(entity.fields.first().setter.name, `is`("id"))
-            assertThat(entity.fields.first().setter.callType, `is`(CallType.FIELD))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun preferPublicOverProtected() {
-        singleEntity("""
-                @PrimaryKey
-                int id;
-                public void setId(int id) {}
-                public int getId(){ return id; }
-                """) { entity, _ ->
-            assertThat(entity.fields.first().setter.name, `is`("setId"))
-            assertThat(entity.fields.first().getter.name, `is`("getId"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun customName() {
-        singleEntity("""
-                @PrimaryKey
-                int x;
-                """, hashMapOf(Pair("tableName", "\"foo_table\""))) { entity, _ ->
-            assertThat(entity.tableName, `is`("foo_table"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun emptyCustomName() {
-        singleEntity("""
-                @PrimaryKey
-                int x;
-                """, hashMapOf(Pair("tableName", "\" \""))) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
-    }
-
-    @Test
-    fun missingPrimaryKey() {
-        singleEntity("""
-                """) { _, _ ->
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.MISSING_PRIMARY_KEY)
-    }
-
-    @Test
-    fun missingColumnAdapter() {
-        singleEntity("""
-                @PrimaryKey
-                public java.util.Date myDate;
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
-    }
-
-    @Test
-    fun dropSubPrimaryKey() {
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                @Embedded
-                Point myPoint;
-                static class Point {
-                    @PrimaryKey
-                    int x;
-                    int y;
-                }
-                """
-        ) { entity, _ ->
-            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
-        }.compilesWithoutError()
-                .withWarningCount(1)
-                .withWarningContaining(ProcessorErrors.embeddedPrimaryKeyIsDropped(
-                        "foo.bar.MyEntity", "x"))
-    }
-
-    @Test
-    fun ignoreDropSubPrimaryKey() {
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                @Embedded
-                @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
-                Point myPoint;
-                static class Point {
-                    @PrimaryKey
-                    int x;
-                    int y;
-                }
-                """
-        ) { entity, _ ->
-            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun notNull() {
-        singleEntity(
-                """
-                @PrimaryKey int id;
-                @NonNull public String name;
-                """
-        ) { entity, _ ->
-            val field = fieldsByName(entity, "name").first()
-            assertThat(field.name, `is`("name"))
-            assertThat(field.columnName, `is`("name"))
-            assertThat(field.nonNull, `is`(true))
-        }.compilesWithoutError()
-    }
-
-    private fun fieldsByName(entity: Pojo, vararg fieldNames: String): List<Field> {
-        return fieldNames
-                .map { name -> entity.fields.find { it.name == name } }
-                .filterNotNull()
-    }
-
-    @Test
-    fun index_simple() {
-        val annotation = mapOf(
-                "indices" to """@Index("foo")"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                """
-                , annotation) { entity, _ ->
-            assertThat(entity.indices, `is`(
-                    listOf(Index(name = "index_MyEntity_foo",
-                            unique = false,
-                            fields = fieldsByName(entity, "foo")))))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun index_fromField() {
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                @ColumnInfo(index = true)
-                public String foo;
-                """) { entity, _ ->
-            assertThat(entity.indices, `is`(
-                    listOf(Index(name = "index_MyEntity_foo",
-                            unique = false,
-                            fields = fieldsByName(entity, "foo")))
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun index_multiColumn() {
-        val annotation = mapOf(
-                "indices" to """@Index({"foo", "id"})"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                """
-                , annotation) { entity, _ ->
-            assertThat(entity.indices, `is`(
-                    listOf(Index(name = "index_MyEntity_foo_id",
-                            unique = false,
-                            fields = fieldsByName(entity, "foo", "id")))
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun index_multiple() {
-        val annotation = mapOf(
-                "indices" to """{@Index({"foo", "id"}), @Index({"bar_column", "foo"})}"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                @ColumnInfo(name = "bar_column")
-                public String bar;
-                """
-                , annotation) { entity, _ ->
-            assertThat(entity.indices, `is`(
-                    listOf(Index(name = "index_MyEntity_foo_id",
-                            unique = false,
-                            fields = fieldsByName(entity, "foo", "id")),
-                            Index(name = "index_MyEntity_bar_column_foo",
-                                    unique = false,
-                                    fields = fieldsByName(entity, "bar", "foo")))
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun index_unique() {
-        val annotation = mapOf(
-                "indices" to """@Index(value = {"foo", "id"}, unique = true)"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                """
-                , annotation) { entity, _ ->
-            assertThat(entity.indices, `is`(
-                    listOf(Index(
-                            name = "index_MyEntity_foo_id",
-                            unique = true,
-                            fields = fieldsByName(entity, "foo", "id")))
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun index_customName() {
-        val annotation = mapOf(
-                "indices" to """@Index(value = {"foo"}, name = "myName")"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                """
-                , annotation) { entity, _ ->
-            assertThat(entity.indices, `is`(
-                    listOf(Index(name = "myName",
-                            unique = false,
-                            fields = fieldsByName(entity, "foo")))
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun index_customTableName() {
-        val annotation = mapOf(
-                "tableName" to "\"MyTable\"",
-                "indices" to """@Index(value = {"foo"})"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                """
-                , annotation) { entity, _ ->
-            assertThat(entity.indices, `is`(
-                    listOf(Index(name = "index_MyTable_foo",
-                            unique = false,
-                            fields = fieldsByName(entity, "foo")))
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun index_empty() {
-        val annotation = mapOf(
-                "indices" to """@Index({})"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                """
-                , annotation) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
-        )
-    }
-
-    @Test
-    fun index_missingColumn() {
-        val annotation = mapOf(
-                "indices" to """@Index({"foo", "bar"})"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                public String foo;
-                """
-                , annotation) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.indexColumnDoesNotExist("bar", listOf("id, foo"))
-        )
-    }
-
-    @Test
-    fun index_nameConflict() {
-        val annotation = mapOf(
-                "indices" to """@Index({"foo"})"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                @ColumnInfo(index = true)
-                public String foo;
-                """
-                , annotation) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.duplicateIndexInEntity("index_MyEntity_foo")
-        )
-    }
-
-    @Test
-    fun index_droppedParentFieldIndex() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    @ColumnInfo(index = true)
-                    String name;
-                    String lastName;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """, baseClass = "foo.bar.Base", jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.indices.isEmpty(), `is`(true))
-        }.compilesWithoutError()
-                .withWarningContaining(
-                        ProcessorErrors.droppedSuperClassFieldIndex(
-                                fieldName = "name",
-                                childEntity = "foo.bar.MyEntity",
-                                superEntity = "foo.bar.Base")
-                )
-    }
-
-    @Test
-    fun index_keptGrandParentEntityIndex() {
-        val grandParent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(indices = @Index({"name", "lastName"}))
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Parent",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-
-                public class Parent extends Base {
-                    String iHaveAField;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Parent",
-                attributes = hashMapOf("inheritSuperIndices" to "true"),
-                jfos = listOf(parent, grandParent)) {
-            entity, _ ->
-            assertThat(entity.indices.size, `is`(1))
-            assertThat(entity.indices.first(),
-                    `is`(Index(name = "index_MyEntity_name_lastName",
-                            unique = false,
-                            fields = fieldsByName(entity, "name", "lastName"))))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun index_keptParentEntityIndex() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(indices = @Index({"name", "lastName"}))
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                attributes = hashMapOf("inheritSuperIndices" to "true"),
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.indices.size, `is`(1))
-            assertThat(entity.indices.first(),
-                    `is`(Index(name = "index_MyEntity_name_lastName",
-                            unique = false,
-                            fields = fieldsByName(entity, "name", "lastName"))))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun index_keptParentFieldIndex() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    @ColumnInfo(index = true)
-                    String name;
-                    String lastName;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                attributes = hashMapOf("inheritSuperIndices" to "true"),
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.indices.size, `is`(1))
-            assertThat(entity.indices.first(),
-                    `is`(Index(name = "index_MyEntity_name",
-                            unique = false,
-                            fields = fieldsByName(entity, "name"))))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun index_droppedGrandParentEntityIndex() {
-        val grandParent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(indices = @Index({"name", "lastName"}))
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Parent",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-
-                public class Parent extends Base {
-                    String iHaveAField;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """, baseClass = "foo.bar.Parent", jfos = listOf(parent, grandParent)) {
-            entity, _ ->
-            assertThat(entity.indices.isEmpty(), `is`(true))
-        }.compilesWithoutError()
-                .withWarningContaining(
-                        ProcessorErrors.droppedSuperClassIndex(
-                                childEntity = "foo.bar.MyEntity",
-                                superEntity = "foo.bar.Base")
-                )
-    }
-
-    @Test
-    fun index_droppedParentEntityIndex() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(indices = @Index({"name", "lastName"}))
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """, baseClass = "foo.bar.Base", jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.indices.isEmpty(), `is`(true))
-        }.compilesWithoutError()
-                .withWarningContaining(
-                        ProcessorErrors.droppedSuperClassIndex(
-                                childEntity = "foo.bar.MyEntity",
-                                superEntity = "foo.bar.Base")
-                )
-    }
-
-    @Test
-    fun index_droppedEmbeddedEntityIndex() {
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                @Embedded
-                public Foo foo;
-                @Entity(indices = {@Index("a")})
-                static class Foo {
-                    @PrimaryKey
-                    @ColumnInfo(name = "foo_id")
-                    int id;
-                    @ColumnInfo(index = true)
-                    public int a;
-                }
-                """) { entity, _ ->
-            assertThat(entity.indices.isEmpty(), `is`(true))
-        }.compilesWithoutError()
-                .withWarningContaining(
-                        ProcessorErrors.droppedEmbeddedIndex(
-                                entityName = "foo.bar.MyEntity.Foo",
-                                fieldPath = "foo",
-                                grandParent = "foo.bar.MyEntity")
-                )
-    }
-
-    @Test
-    fun index_onEmbeddedField() {
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                @Embedded
-                @ColumnInfo(index = true)
-                public Foo foo;
-                static class Foo {
-                    @ColumnInfo(index = true)
-                    public int a;
-                }
-                """) { entity, _ ->
-            assertThat(entity.indices.isEmpty(), `is`(true))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION
-        )
-    }
-
-    @Test
-    fun index_droppedEmbeddedFieldIndex() {
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                @Embedded
-                public Foo foo;
-                static class Foo {
-                    @ColumnInfo(index = true)
-                    public int a;
-                }
-                """) { entity, _ ->
-            assertThat(entity.indices.isEmpty(), `is`(true))
-        }.compilesWithoutError()
-                .withWarningContaining(
-                        ProcessorErrors.droppedEmbeddedFieldIndex("foo > a", "foo.bar.MyEntity")
-                )
-    }
-
-    @Test
-    fun index_referenceEmbeddedField() {
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                @Embedded
-                public Foo foo;
-                static class Foo {
-                    public int a;
-                }
-                """, attributes = mapOf("indices" to "@Index(\"a\")")) { entity, _ ->
-            assertThat(entity.indices.size, `is`(1))
-            assertThat(entity.indices.first(), `is`(
-                    Index(
-                            name = "index_MyEntity_a",
-                            unique = false,
-                            fields = fieldsByName(entity, "a")
-                    )
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun primaryKey_definedInBothWays() {
-        singleEntity(
-                """
-                public int id;
-                @PrimaryKey
-                public String foo;
-                """,
-                attributes = mapOf("primaryKeys" to "\"id\"")) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.multiplePrimaryKeyAnnotations(
-                        listOf("PrimaryKey[id]", "PrimaryKey[foo]")
-                ))
-    }
-
-    @Test
-    fun primaryKey_badColumnName() {
-        singleEntity(
-                """
-                public int id;
-                """,
-                attributes = mapOf("primaryKeys" to "\"foo\"")) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.primaryKeyColumnDoesNotExist("foo", listOf("id")))
-    }
-
-    @Test
-    fun primaryKey_multipleAnnotations() {
-        singleEntity("""
-                @PrimaryKey
-                int x;
-                @PrimaryKey
-                int y;
-                """) { entity, _ ->
-            assertThat(entity.primaryKey.fields.isEmpty(), `is`(true))
-        }.failsToCompile()
-                .withErrorContaining(
-                        ProcessorErrors.multiplePrimaryKeyAnnotations(
-                                listOf("PrimaryKey[x]", "PrimaryKey[y]")))
-    }
-
-    @Test
-    fun primaryKey_fromParentField() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun primaryKey_fromParentEntity() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(primaryKeys = "baseId")
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun primaryKey_overrideFromParentField() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                public class Base {
-                    @PrimaryKey
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.primaryKey.fields.size, `is`(1))
-            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
-        }.compilesWithoutError().withNoteContaining(
-                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
-        )
-    }
-
-    @Test
-    fun primaryKey_overrideFromParentEntityViaField() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(primaryKeys = "baseId")
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.primaryKey.fields.size, `is`(1))
-            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-        }.compilesWithoutError().withNoteContaining(
-                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
-        )
-    }
-
-    @Test
-    fun primaryKey_overrideFromParentEntityViaEntity() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity(primaryKeys = "baseId")
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent),
-                attributes = mapOf("primaryKeys" to "\"id\"")) { entity, _ ->
-            assertThat(entity.primaryKey.fields.size, `is`(1))
-            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
-        }.compilesWithoutError().withNoteContaining(
-                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
-        )
-    }
-
-    @Test
-    fun primaryKey_autoGenerate() {
-        listOf("long", "Long", "Integer", "int").forEach { type ->
-            singleEntity(
-                    """
-                @PrimaryKey(autoGenerate = true)
-                public $type id;
-                """) { entity, _ ->
-                assertThat(entity.primaryKey.fields.size, `is`(1))
-                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun primaryKey_nonNull_notNeeded() {
-        listOf("long", "Long", "Integer", "int").forEach { type ->
-            singleEntity(
-                    """
-                @PrimaryKey
-                public $type id;
-                """) { entity, _ ->
-                assertThat(entity.primaryKey.fields.size, `is`(1))
-                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-                assertThat(entity.primaryKey.autoGenerateId, `is`(false))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun primaryKey_autoGenerateBadType() {
-        listOf("String", "float", "Float", "Double", "double").forEach { type ->
-            singleEntity(
-                    """
-                @PrimaryKey(autoGenerate = true)
-                public $type id;
-                """) { entity, _ ->
-                assertThat(entity.primaryKey.fields.size, `is`(1))
-                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
-            }.failsToCompile().withErrorContaining(
-                    ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT)
-        }
-    }
-
-    @Test
-    fun primaryKey_embedded() {
-        singleEntity(
-                """
-                public int id;
-
-                @Embedded(prefix = "bar_")
-                @PrimaryKey
-                @NonNull
-                public Foo foo;
-
-                static class Foo {
-                    public int a;
-                    public int b;
-                }
-                """) { entity, _ ->
-            assertThat(entity.primaryKey.fields.map { it.columnName },
-                    `is`(listOf("bar_a", "bar_b")))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun primaryKey_embeddedInherited() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                    @Embedded(prefix = "bar_")
-                    @PrimaryKey
-                    @NonNull
-                    public Foo foo;
-
-                    static class Foo {
-                        public int a;
-                        public int b;
-                    }
-                }
-                """)
-        singleEntity(
-                """
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.primaryKey.fields.map { it.columnName },
-                    `is`(listOf("bar_a", "bar_b")))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun primaryKey_overrideViaEmbedded() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-
-                @Entity(primaryKeys = "baseId")
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                public int id;
-                @Embedded(prefix = "bar_")
-                @PrimaryKey
-                @NonNull
-                public Foo foo;
-
-                static class Foo {
-                    public int a;
-                    public int b;
-                }
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.primaryKey.fields.map { it.columnName },
-                    `is`(listOf("bar_a", "bar_b")))
-        }.compilesWithoutError().withNoteContaining(
-                "PrimaryKey[baseId] is overridden by PrimaryKey[foo > a, foo > b]")
-    }
-
-    @Test
-    fun primaryKey_overrideEmbedded() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                    @Embedded(prefix = "bar_")
-                    @PrimaryKey
-                    @NonNull
-                    public Foo foo;
-
-                    static class Foo {
-                        public int a;
-                        public int b;
-                    }
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { entity, _ ->
-            assertThat(entity.primaryKey.fields.map { it.columnName },
-                    `is`(listOf("id")))
-        }.compilesWithoutError().withNoteContaining(
-                "PrimaryKey[foo > a, foo > b] is overridden by PrimaryKey[id]")
-    }
-
-    @Test
-    fun primaryKey_NonNull() {
-        singleEntity(
-                """
-            @PrimaryKey
-            @NonNull
-            public String id;
-            """) { entity, _ ->
-            assertThat(entity.primaryKey.fields.size, `is`(1))
-            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun primaryKey_Nullable() {
-        singleEntity(
-                """
-            @PrimaryKey
-            public String id;
-            """) { entity, _ ->
-            assertThat(entity.primaryKey.fields.size, `is`(1))
-            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("id"))
-    }
-
-    @Test
-    fun primaryKey_MultipleNullable() {
-        singleEntity(
-                """
-            @PrimaryKey
-            public String id;
-            @PrimaryKey
-            public String anotherId;
-            """) { entity, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("id"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("anotherId"))
-    }
-
-    @Test
-    fun primaryKey_MultipleNullableAndNonNullable() {
-        singleEntity(
-                """
-            @PrimaryKey
-            @NonNull
-            public String id;
-            @PrimaryKey
-            public String anotherId;
-            """) { entity, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("anotherId"))
-    }
-
-    @Test
-    fun primaryKey_definedAsAttributesNullable() {
-        singleEntity(
-                """
-                public int id;
-                public String foo;
-                """,
-                attributes = mapOf("primaryKeys" to "{\"id\", \"foo\"}")) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-    }
-
-    @Test
-    fun primaryKey_definedAsAttributesNonNull() {
-        singleEntity(
-                """
-                public int id;
-                @NonNull
-                public String foo;
-                """,
-                attributes = mapOf("primaryKeys" to "{\"id\", \"foo\"}")) { entity, _ ->
-            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id", "foo")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun primaryKey_nullableEmbedded() {
-        singleEntity(
-                """
-                public int id;
-
-                @Embedded(prefix = "bar_")
-                @PrimaryKey
-                public Foo foo;
-
-                static class Foo {
-                    public int a;
-                    public int b;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-    }
-
-    @Test
-    fun primaryKey_nullableEmbeddedObject() {
-        singleEntity(
-                """
-                public int id;
-
-                @Embedded(prefix = "bar_")
-                @PrimaryKey
-                public Foo foo;
-
-                static class Foo {
-                    public String a;
-                    public String b;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-                .and().withErrorCount(3)
-    }
-
-    @Test
-    fun primaryKey_nullableEmbeddedObject_multipleParents() {
-        singleEntity(
-                """
-                public int id;
-
-                @Embedded(prefix = "bar_")
-                @PrimaryKey
-                public Foo foo;
-
-                static class Foo {
-                @Embedded(prefix = "baz_")
-                public Baz a;
-                public String b;
-
-                static class Baz {
-                    public Integer bb;
-                }
-            }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a > bb"))
-                .and().withErrorCount(4)
-    }
-
-    @Test
-    fun primaryKey_nullableEmbeddedInherited() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                    @Embedded(prefix = "bar_")
-                    @PrimaryKey
-                    public Foo foo;
-
-                    static class Foo {
-                        public int a;
-                        public int b;
-                    }
-                }
-                """)
-        singleEntity(
-                """
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
-                .and().withErrorCount(3)
-    }
-
-    @Test
-    fun primaryKey_nullableOverrideViaEmbedded() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-
-                @Entity(primaryKeys = "baseId")
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                }
-                """)
-        singleEntity(
-                """
-                public int id;
-                @Embedded(prefix = "bar_")
-                @PrimaryKey
-                public Foo foo;
-
-                static class Foo {
-                    public int a;
-                    public int b;
-                }
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
-                .and().withNoteContaining(
-                "PrimaryKey[baseId] is overridden by PrimaryKey[foo > a, foo > b]")
-                .and().withErrorCount(3)
-    }
-
-    @Test
-    fun primaryKey_nullableOverrideEmbedded() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                    @Embedded(prefix = "bar_")
-                    @PrimaryKey
-                    public Foo foo;
-
-                    static class Foo {
-                        public int a;
-                        public int b;
-                    }
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
-                .and().withNoteContaining(
-                "PrimaryKey[foo > a, foo > b] is overridden by PrimaryKey[id]")
-                .and().withErrorCount(3)
-    }
-
-    @Test
-    fun primaryKey_integerOverrideEmbedded() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                    @Embedded(prefix = "bar_")
-                    @PrimaryKey
-                    public Foo foo;
-
-                    static class Foo {
-                        public Integer a;
-                    }
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { _, _ ->
-        }.compilesWithoutError().withNoteContaining(
-                "PrimaryKey[foo > a] is overridden by PrimaryKey[id]")
-    }
-
-    @Test
-    fun primaryKey_singleStringPrimaryKeyOverrideEmbedded() {
-        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
-                """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-
-                public class Base {
-                    long baseId;
-                    String name, lastName;
-                    @Embedded(prefix = "bar_")
-                    @PrimaryKey
-                    public Foo foo;
-
-                    static class Foo {
-                        public String a;
-                    }
-                }
-                """)
-        singleEntity(
-                """
-                @PrimaryKey
-                public int id;
-                """,
-                baseClass = "foo.bar.Base",
-                jfos = listOf(parent)) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
-                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
-                .and().withNoteContaining(
-                "PrimaryKey[foo > a] is overridden by PrimaryKey[id]")
-                .and().withErrorCount(2)
-    }
-
-    @Test
-    fun relationInEntity() {
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                java.util.List<User> users;
-                """, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(RELATION_IN_ENTITY)
-    }
-
-    @Test
-    fun foreignKey_invalidAction() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = "name",
-                    onDelete = 101
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
-    }
-
-    @Test
-    fun foreignKey_badEntity() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = dsa.class,
-                    parentColumns = "lastName",
-                    childColumns = "name"
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining("cannot find symbol")
-    }
-
-    @Test
-    fun foreignKey_notAnEntity() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = "name"
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.NOT_AN_ENTITY)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyNotAnEntity(
-                COMMON.NOT_AN_ENTITY_TYPE_NAME.toString()))
-    }
-
-    @Test
-    fun foreignKey_invalidChildColumn() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = "namex"
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyChildColumnDoesNotExist(
-                "namex", listOf("id", "name")))
-    }
-
-    @Test
-    fun foreignKey_columnCountMismatch() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = {"name", "id"}
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyColumnNumberMismatch(
-                listOf("name", "id"), listOf("lastName")))
-    }
-
-    @Test
-    fun foreignKey_emptyChildColumns() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = {}
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST)
-    }
-
-    @Test
-    fun foreignKey_emptyParentColumns() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = {},
-                    childColumns = {"name"}
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST)
-    }
-
-    @Test
-    fun foreignKey_simple() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = "name",
-                    onDelete = ForeignKey.SET_NULL,
-                    onUpdate = ForeignKey.CASCADE,
-                    deferred = true
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { entity, _ ->
-            assertThat(entity.foreignKeys.size, `is`(1))
-            val fKey = entity.foreignKeys.first()
-            assertThat(fKey.parentTable, `is`("User"))
-            assertThat(fKey.parentColumns, `is`(listOf("lastName")))
-            assertThat(fKey.deferred, `is`(true))
-            assertThat(fKey.childFields.size, `is`(1))
-            val field = fKey.childFields.first()
-            assertThat(field.name, `is`("name"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun foreignKey_dontDuplicationChildIndex_SingleColumn() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = "name",
-                    onDelete = ForeignKey.SET_NULL,
-                    onUpdate = ForeignKey.CASCADE,
-                    deferred = true
-                )}""".trimIndent(),
-                "indices" to """@Index("name")"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.compilesWithoutWarnings()
-    }
-
-    @Test
-    fun foreignKey_dontDuplicationChildIndex_MultipleColumns() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = {"lastName", "name"},
-                    childColumns = {"lName", "name"},
-                    onDelete = ForeignKey.SET_NULL,
-                    onUpdate = ForeignKey.CASCADE,
-                    deferred = true
-                )}""".trimIndent(),
-                "indices" to """@Index({"lName", "name"})"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                String lName;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { entity, _ ->
-            assertThat(entity.indices.size, `is`(1))
-        }.compilesWithoutWarnings()
-    }
-
-    @Test
-    fun foreignKey_dontDuplicationChildIndex_WhenCovered() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = {"lastName"},
-                    childColumns = {"name"},
-                    onDelete = ForeignKey.SET_NULL,
-                    onUpdate = ForeignKey.CASCADE,
-                    deferred = true
-                )}""".trimIndent(),
-                "indices" to """@Index({"name", "lName"})"""
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                String lName;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { entity, _ ->
-            assertThat(entity.indices.size, `is`(1))
-        }.compilesWithoutWarnings()
-    }
-
-    @Test
-    fun foreignKey_warnMissingChildIndex() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = "name",
-                    onDelete = ForeignKey.SET_NULL,
-                    onUpdate = ForeignKey.CASCADE,
-                    deferred = true
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { entity, _ ->
-            assertThat(entity.indices, `is`(emptyList()))
-        }.compilesWithoutError().withWarningContaining(
-                ProcessorErrors.foreignKeyMissingIndexInChildColumn("name"))
-    }
-
-    @Test
-    fun foreignKey_warnMissingChildrenIndex() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = {"lastName", "name"},
-                    childColumns = {"lName", "name"}
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                String lName;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { entity, _ ->
-            assertThat(entity.indices, `is`(emptyList()))
-        }.compilesWithoutError().withWarningContaining(
-                ProcessorErrors.foreignKeyMissingIndexInChildColumns(listOf("lName", "name")))
-    }
-
-    @Test
-    fun foreignKey_dontIndexIfAlreadyPrimaryKey() {
-        val annotation = mapOf(
-                "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
-                    parentColumns = "lastName",
-                    childColumns = "id",
-                    onDelete = ForeignKey.SET_NULL,
-                    onUpdate = ForeignKey.CASCADE,
-                    deferred = true
-                )}""".trimIndent()
-        )
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { entity, _ ->
-            assertThat(entity.indices, `is`(emptyList()))
-        }.compilesWithoutWarnings()
-    }
-
-    @Test
-    fun recursion_1Level() {
-        singleEntity(
-                """
-                @Embedded
-                MyEntity myEntity;
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyEntity -> foo.bar.MyEntity"))
-    }
-
-    @Test
-    fun recursion_2Levels_embedToRelation() {
-        singleEntity(
-                """
-                int pojoId;
-                @Embedded
-                A a;
-
-                static class A {
-                    int entityId;
-                    @Relation(parentColumn = "pojoId", entityColumn = "entityId")
-                    MyEntity myEntity;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
-    }
-
-    @Test
-    fun recursion_2Levels_onlyEmbeds_entityToPojo() {
-        singleEntity(
-                """
-                @Embedded
-                A a;
-
-                static class A {
-                    @Embedded
-                    MyEntity myEntity;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
-    }
-
-    @Test
-    fun recursion_2Levels_onlyEmbeds_onlyEntities() {
-        singleEntity(
-                """
-                @Embedded
-                A a;
-
-                @Entity
-                static class A {
-                    @Embedded
-                    MyEntity myEntity;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
-    }
-    fun okTableName() {
-        val annotation = mapOf("tableName" to "\"foo bar\"")
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun badTableName() {
-        val annotation = mapOf("tableName" to """ "foo`bar" """)
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                String name;
-                """,
-                attributes = annotation, jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_TABLE_NAME)
-    }
-
-    @Test
-    fun badColumnName() {
-        singleEntity(
-                """
-                @PrimaryKey
-                int id;
-                @ColumnInfo(name = "\"foo bar\"")
-                String name;
-                """,
-                jfos = listOf(COMMON.USER)
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_COLUMN_NAME)
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/FieldProcessorTest.kt
deleted file mode 100644
index ef0170c..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/FieldProcessorTest.kt
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.parser.Collate
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.solver.types.ColumnTypeAdapter
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.Field
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import simpleRun
-import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-
-@Suppress("HasPlatformType")
-@RunWith(JUnit4::class)
-class FieldProcessorTest {
-    companion object {
-        const val ENTITY_PREFIX = """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                @Entity
-                abstract class MyEntity {
-                """
-        const val ENTITY_SUFFIX = "}"
-        val ALL_PRIMITIVES = arrayListOf(
-                TypeKind.INT,
-                TypeKind.BYTE,
-                TypeKind.SHORT,
-                TypeKind.LONG,
-                TypeKind.CHAR,
-                TypeKind.FLOAT,
-                TypeKind.DOUBLE)
-        val ARRAY_CONVERTER = JavaFileObjects.forSourceLines("foo.bar.MyConverter",
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                public class MyConverter {
-                ${ALL_PRIMITIVES.joinToString("\n") {
-                    val arrayDef = "${it.name.toLowerCase()}[]"
-                    "@TypeConverter public static String" +
-                            " arrayIntoString($arrayDef input) { return null;}" +
-                            "@TypeConverter public static $arrayDef" +
-                            " stringIntoArray${it.name}(String input) { return null;}"
-                }}
-                ${ALL_PRIMITIVES.joinToString("\n") {
-                    val arrayDef = "${it.box()}[]"
-                    "@TypeConverter public static String" +
-                            " arrayIntoString($arrayDef input) { return null;}" +
-                            "@TypeConverter public static $arrayDef" +
-                            " stringIntoArray${it.name}Boxed(String input) { return null;}"
-                }}
-                }
-                """)
-
-        private fun TypeKind.box(): String {
-            return "java.lang." + when (this) {
-                TypeKind.INT -> "Integer"
-                TypeKind.CHAR -> "Character"
-                else -> this.name.toLowerCase().capitalize()
-            }
-        }
-
-        // these 2 box methods are ugly but makes tests nicer and they are private
-        private fun TypeKind.typeMirror(invocation: TestInvocation): TypeMirror {
-            return invocation.processingEnv.typeUtils.getPrimitiveType(this)
-        }
-
-        private fun TypeKind.affinity(): SQLTypeAffinity {
-            return when (this) {
-                TypeKind.FLOAT, TypeKind.DOUBLE -> SQLTypeAffinity.REAL
-                else -> SQLTypeAffinity.INTEGER
-            }
-        }
-
-        private fun TypeKind.box(invocation: TestInvocation): TypeMirror {
-            return invocation.processingEnv.elementUtils.getTypeElement(box()).asType()
-        }
-    }
-
-    @Test
-    fun primitives() {
-        ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("${primitive.name.toLowerCase()} x;") { field, invocation ->
-                assertThat(field, `is`(
-                        Field(name = "x",
-                                type = primitive.typeMirror(invocation),
-                                element = field.element,
-                                affinity = primitive.affinity()
-                        )))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun boxed() {
-        ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("${primitive.box()} y;") { field, invocation ->
-                assertThat(field, `is`(
-                        Field(name = "y",
-                                type = primitive.box(invocation),
-                                element = field.element,
-                                affinity = primitive.affinity())))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun columnName() {
-        singleEntity("""
-            @ColumnInfo(name = "foo")
-            @PrimaryKey
-            int x;
-            """) { field, invocation ->
-            assertThat(field, `is`(
-                    Field(name = "x",
-                            type = TypeKind.INT.typeMirror(invocation),
-                            element = field.element,
-                            columnName = "foo",
-                            affinity = SQLTypeAffinity.INTEGER)))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun indexed() {
-        singleEntity("""
-            @ColumnInfo(name = "foo", index = true)
-            int x;
-            """) { field, invocation ->
-            assertThat(field, `is`(
-                    Field(name = "x",
-                            type = TypeKind.INT.typeMirror(invocation),
-                            element = field.element,
-                            columnName = "foo",
-                            affinity = SQLTypeAffinity.INTEGER,
-                            indexed = true)))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun emptyColumnName() {
-        singleEntity("""
-            @ColumnInfo(name = "")
-            int x;
-            """) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
-    }
-
-    @Test
-    fun byteArrayWithEnforcedType() {
-        singleEntity("@TypeConverters(foo.bar.MyConverter.class)" +
-                "@ColumnInfo(typeAffinity = ColumnInfo.TEXT) byte[] arr;") { field, invocation ->
-            assertThat(field, `is`(Field(name = "arr",
-                    type = invocation.processingEnv.typeUtils.getArrayType(
-                            TypeKind.BYTE.typeMirror(invocation)),
-                    element = field.element,
-                    affinity = SQLTypeAffinity.TEXT)))
-            assertThat((field.cursorValueReader as? ColumnTypeAdapter)?.typeAffinity,
-                    `is`(SQLTypeAffinity.TEXT))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun primitiveArray() {
-        ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
-                    "${primitive.toString().toLowerCase()}[] arr;") { field, invocation ->
-                assertThat(field, `is`(
-                        Field(name = "arr",
-                                type = invocation.processingEnv.typeUtils.getArrayType(
-                                        primitive.typeMirror(invocation)),
-                                element = field.element,
-                                affinity = if (primitive == TypeKind.BYTE) {
-                                    SQLTypeAffinity.BLOB
-                                } else {
-                                    SQLTypeAffinity.TEXT
-                                })))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun boxedArray() {
-        ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
-                    "${primitive.box()}[] arr;") { field, invocation ->
-                assertThat(field, `is`(
-                        Field(name = "arr",
-                                type = invocation.processingEnv.typeUtils.getArrayType(
-                                        primitive.box(invocation)),
-                                element = field.element,
-                                affinity = SQLTypeAffinity.TEXT)))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun generic() {
-        singleEntity("""
-                static class BaseClass<T> {
-                    T item;
-                }
-                @Entity
-                static class Extending extends BaseClass<java.lang.Integer> {
-                }
-                """) { field, invocation ->
-            assertThat(field, `is`(Field(name = "item",
-                    type = TypeKind.INT.box(invocation),
-                    element = field.element,
-                    affinity = SQLTypeAffinity.INTEGER)))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun unboundGeneric() {
-        singleEntity("""
-                @Entity
-                static class BaseClass<T> {
-                    T item;
-                }
-                """) { _, _ -> }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
-    }
-
-    @Test
-    fun nameVariations() {
-        simpleRun {
-            assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
-            assertThat(Field(mock(Element::class.java), "x", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
-            assertThat(Field(mock(Element::class.java), "xAll",
-                    TypeKind.BOOLEAN.typeMirror(it), SQLTypeAffinity.INTEGER)
-                    .nameWithVariations, `is`(arrayListOf("xAll")))
-        }
-    }
-
-    @Test
-    fun nameVariations_is() {
-        val elm = mock(Element::class.java)
-        simpleRun {
-            assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX", "x")))
-            assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX")))
-            assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("is")))
-            assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations,
-                    `is`(arrayListOf("isAllItems", "allItems")))
-        }
-    }
-
-    @Test
-    fun nameVariations_has() {
-        val elm = mock(Element::class.java)
-        simpleRun {
-            assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX", "x")))
-            assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX")))
-            assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("has")))
-            assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations,
-                    `is`(arrayListOf("hasAllItems", "allItems")))
-        }
-    }
-
-    @Test
-    fun nameVariations_m() {
-        val elm = mock(Element::class.java)
-        simpleRun {
-            assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mall")))
-            assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mallVars")))
-            assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mAll", "all")))
-            assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("m")))
-            assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations,
-                    `is`(arrayListOf("mallItems")))
-            assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations,
-                    `is`(arrayListOf("mAllItems", "allItems")))
-        }
-    }
-
-    @Test
-    fun nameVariations_underscore() {
-        val elm = mock(Element::class.java)
-        simpleRun {
-            assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_all", "all")))
-            assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_")))
-            assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it),
-                    SQLTypeAffinity.INTEGER).nameWithVariations,
-                    `is`(arrayListOf("_allItems", "allItems")))
-        }
-    }
-
-    @Test
-    fun collate() {
-        Collate.values().forEach { collate ->
-            singleEntity("""
-            @PrimaryKey
-            @ColumnInfo(collate = ColumnInfo.${collate.name})
-            String code;
-            """) { field, invocation ->
-                assertThat(field, `is`(
-                        Field(name = "code",
-                                type = invocation.context.COMMON_TYPES.STRING,
-                                element = field.element,
-                                columnName = "code",
-                                collate = collate,
-                                affinity = SQLTypeAffinity.TEXT)))
-            }.compilesWithoutError()
-        }
-    }
-
-    fun singleEntity(vararg input: String, handler: (Field, invocation: TestInvocation) -> Unit):
-            CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
-                        ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
-                ), ARRAY_CONVERTER))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(android.arch.persistence.room.Entity::class)
-                        .nextRunHandler { invocation ->
-                            val (owner, field) = invocation.roundEnv
-                                    .getElementsAnnotatedWith(Entity::class.java)
-                                    .map {
-                                        Pair(it, invocation.processingEnv.elementUtils
-                                                .getAllMembers(MoreElements.asType(it))
-                                                .firstOrNull { it.kind == ElementKind.FIELD })
-                                    }
-                                    .first { it.second != null }
-                            val entityContext =
-                                    EntityProcessor(invocation.context, MoreElements.asType(owner))
-                                            .context
-                            val parser = FieldProcessor(
-                                    baseContext = entityContext,
-                                    containing = MoreTypes.asDeclared(owner.asType()),
-                                    element = field!!,
-                                    bindingScope = FieldProcessor.BindingScope.TWO_WAY,
-                                    fieldParent = null)
-                            handler(parser.process(), invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessorTest.kt
deleted file mode 100644
index 51f81db..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/InsertionMethodProcessorTest.kt
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import COMMON
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.OnConflictStrategy
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.InsertionMethod
-import android.arch.persistence.room.vo.InsertionMethod.Type
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth.assertAbout
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-@RunWith(JUnit4::class)
-class InsertionMethodProcessorTest {
-    companion object {
-        const val DAO_PREFIX = """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                import java.util.*;
-                @Dao
-                abstract class MyClass {
-                """
-        const val DAO_SUFFIX = "}"
-        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
-        val BOOK_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Book")
-    }
-
-    @Test
-    fun readNoParams() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void foo();
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("foo"))
-            assertThat(insertion.parameters.size, `is`(0))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-            assertThat(insertion.entities.size, `is`(0))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT)
-    }
-
-    @Test
-    fun insertSingle() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public long foo(User user);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("foo"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName(), `is`(USER_TYPE_NAME))
-            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities["user"]?.typeName,
-                    `is`(ClassName.get("foo.bar", "User") as TypeName))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.LONG))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertNotAnEntity() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void foo(NotAnEntity notValid);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("foo"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.entityType, `is`(nullValue()))
-            assertThat(insertion.entities.size, `is`(0))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
-        )
-    }
-
-    @Test
-    fun insertTwo() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void foo(User u1, User u2);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("foo"))
-
-            assertThat(insertion.parameters.size, `is`(2))
-            insertion.parameters.forEach {
-                assertThat(it.type.typeName(), `is`(USER_TYPE_NAME))
-                assertThat(it.entityType?.typeName(), `is`(USER_TYPE_NAME))
-            }
-            assertThat(insertion.entities.size, `is`(2))
-            assertThat(insertion.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities["u2"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "u2")))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertList() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public List<Long> insertUsers(List<User> users);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ParameterizedTypeName.get(
-                            ClassName.get("java.util", "List"), USER_TYPE_NAME) as TypeName))
-            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName(), `is`(
-                    ParameterizedTypeName.get(ClassName.get("java.util", "List"),
-                            ClassName.get("java.lang", "Long")) as TypeName
-            ))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertArray() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void insertUsers(User[] users);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertSet() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void insertUsers(Set<User> users);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ParameterizedTypeName.get(ClassName.get("java.util", "Set")
-                            , COMMON.USER_TYPE_NAME) as TypeName))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertQueue() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void insertUsers(Queue<User> users);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("insertUsers"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ParameterizedTypeName.get(ClassName.get("java.util", "Queue")
-                            , USER_TYPE_NAME) as TypeName))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertIterable() {
-        singleInsertMethod("""
-                @Insert
-                abstract public void insert(Iterable<User> users);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("insert"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName(), `is`(ParameterizedTypeName.get(
-                    ClassName.get("java.lang", "Iterable"), USER_TYPE_NAME) as TypeName))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertCustomCollection() {
-        singleInsertMethod("""
-                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
-                @Insert
-                abstract public void insert(MyList<String, User> users);
-                """) { insertion, _ ->
-            assertThat(insertion.name, `is`("insert"))
-            assertThat(insertion.parameters.size, `is`(1))
-            val param = insertion.parameters.first()
-            assertThat(param.type.typeName(), `is`(ParameterizedTypeName.get(
-                    ClassName.get("foo.bar", "MyClass.MyList"),
-                    CommonTypeNames.STRING, USER_TYPE_NAME) as TypeName))
-            assertThat(insertion.entities.size, `is`(1))
-            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun insertDifferentTypes() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void foo(User u1, Book b1);
-                """) { insertion, _ ->
-            assertThat(insertion.parameters.size, `is`(2))
-            assertThat(insertion.parameters[0].type.typeName().toString(),
-                    `is`("foo.bar.User"))
-            assertThat(insertion.parameters[1].type.typeName().toString(),
-                    `is`("foo.bar.Book"))
-            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "b1")))
-            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
-            assertThat(insertion.entities.size, `is`(2))
-            assertThat(insertion.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(insertion.entities["b1"]?.typeName, `is`(BOOK_TYPE_NAME))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun onConflict_Default() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public void foo(User user);
-                """) { insertion, _ ->
-            assertThat(insertion.onConflict, `is`(OnConflictStrategy.ABORT))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun onConflict_Invalid() {
-        singleInsertMethod(
-                """
-                @Insert(onConflict = -1)
-                abstract public void foo(User user);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
-    }
-
-    @Test
-    fun onConflict_EachValue() {
-        listOf(
-                Pair("REPLACE", 1),
-                Pair("ROLLBACK", 2),
-                Pair("ABORT", 3),
-                Pair("FAIL", 4),
-                Pair("IGNORE", 5)
-        ).forEach { pair ->
-            singleInsertMethod(
-                    """
-                @Insert(onConflict=OnConflictStrategy.${pair.first})
-                abstract public void foo(User user);
-                """) { insertion, _ ->
-                assertThat(insertion.onConflict, `is`(pair.second))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun invalidReturnType() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public int foo(User user);
-                """) { insertion, _ ->
-            assertThat(insertion.insertionType, `is`(nullValue()))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE)
-    }
-
-    @Test
-    fun mismatchedReturnType() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public long[] foo(User user);
-                """) { insertion, _ ->
-            assertThat(insertion.insertionType, `is`(nullValue()))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.insertionMethodReturnTypeMismatch(
-                        ArrayTypeName.of(TypeName.LONG),
-                        InsertionMethodProcessor.SINGLE_ITEM_SET.map { it.returnTypeName }))
-    }
-
-    @Test
-    fun mismatchedReturnType2() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public long foo(User... user);
-                """) { insertion, _ ->
-            assertThat(insertion.insertionType, `is`(nullValue()))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.insertionMethodReturnTypeMismatch(
-                        TypeName.LONG,
-                        InsertionMethodProcessor.MULTIPLE_ITEM_SET.map { it.returnTypeName }))
-    }
-
-    @Test
-    fun mismatchedReturnType3() {
-        singleInsertMethod(
-                """
-                @Insert
-                abstract public long foo(User user1, User user2);
-                """) { insertion, _ ->
-            assertThat(insertion.insertionType, `is`(nullValue()))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.insertionMethodReturnTypeMismatch(
-                        TypeName.LONG,
-                        InsertionMethodProcessor.VOID_SET.map { it.returnTypeName }))
-    }
-
-    @Test
-    fun validReturnTypes() {
-        listOf(
-                Pair("void", Type.INSERT_VOID),
-                Pair("long", Type.INSERT_SINGLE_ID),
-                Pair("long[]", Type.INSERT_ID_ARRAY),
-                Pair("Long[]", Type.INSERT_ID_ARRAY_BOX),
-                Pair("List<Long>", Type.INSERT_ID_LIST)
-        ).forEach { pair ->
-            val dots = if (pair.second in setOf(Type.INSERT_ID_LIST, Type.INSERT_ID_ARRAY,
-                    Type.INSERT_ID_ARRAY_BOX)) {
-                "..."
-            } else {
-                ""
-            }
-            singleInsertMethod(
-                    """
-                @Insert
-                abstract public ${pair.first} foo(User$dots user);
-                """) { insertion, _ ->
-                assertThat(insertion.insertMethodTypeFor(insertion.parameters.first()),
-                        `is`(pair.second))
-                assertThat(pair.toString(), insertion.insertionType, `is`(pair.second))
-            }.compilesWithoutError()
-        }
-    }
-
-    fun singleInsertMethod(
-            vararg input: String,
-            handler: (InsertionMethod, TestInvocation) -> Unit
-    ): CompileTester {
-        return assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
-                ), COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(Insert::class, Dao::class)
-                        .nextRunHandler { invocation ->
-                            val (owner, methods) = invocation.roundEnv
-                                    .getElementsAnnotatedWith(Dao::class.java)
-                                    .map {
-                                        Pair(it,
-                                                invocation.processingEnv.elementUtils
-                                                        .getAllMembers(MoreElements.asType(it))
-                                                        .filter {
-                                                            MoreElements.isAnnotationPresent(it,
-                                                                    Insert::class.java)
-                                                        }
-                                        )
-                                    }.filter { it.second.isNotEmpty() }.first()
-                            val processor = InsertionMethodProcessor(
-                                    baseContext = invocation.context,
-                                    containing = MoreTypes.asDeclared(owner.asType()),
-                                    executableElement = MoreElements.asExecutable(methods.first()))
-                            val processed = processor.process()
-                            handler(processed, invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt
deleted file mode 100644
index c12424d..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/PojoProcessorTest.kt
+++ /dev/null
@@ -1,958 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import COMMON
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
-import android.arch.persistence.room.processor.ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY
-import android.arch.persistence.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
-import android.arch.persistence.room.processor.ProcessorErrors.RELATION_NOT_COLLECTION
-import android.arch.persistence.room.processor.ProcessorErrors.relationCannotFindEntityField
-import android.arch.persistence.room.processor.ProcessorErrors.relationCannotFindParentEntityField
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.vo.CallType
-import android.arch.persistence.room.vo.Constructor
-import android.arch.persistence.room.vo.EmbeddedField
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.Pojo
-import android.arch.persistence.room.vo.RelationCollector
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.instanceOf
-import org.hamcrest.CoreMatchers.not
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.CoreMatchers.sameInstance
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import simpleRun
-import javax.lang.model.element.Element
-import javax.tools.JavaFileObject
-
-/**
- * Some of the functionality is tested via EntityProcessor.
- */
-@RunWith(JUnit4::class)
-class PojoProcessorTest {
-
-    companion object {
-        val MY_POJO: ClassName = ClassName.get("foo.bar", "MyPojo")
-        val HEADER = """
-            package foo.bar;
-            import android.arch.persistence.room.*;
-            import java.util.*;
-            public class MyPojo {
-            """
-        val FOOTER = "\n}"
-    }
-
-    private fun String.toJFO(qName: String) = JavaFileObjects.forSourceLines(qName, this)
-
-    @Test
-    fun inheritedPrivate() {
-        val parent = """
-            package foo.bar.x;
-            import android.arch.persistence.room.*;
-            public class BaseClass {
-                private String baseField;
-                public String getBaseField(){ return baseField; }
-                public void setBaseField(String baseField){ }
-            }
-        """
-        simpleRun(
-                """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                public class ${MY_POJO.simpleName()} extends foo.bar.x.BaseClass {
-                    public String myField;
-                }
-                """.toJFO(MY_POJO.toString()),
-                parent.toJFO("foo.bar.x.BaseClass")) { invocation ->
-            val pojo = PojoProcessor(baseContext = invocation.context,
-                    element = invocation.typeElement(MY_POJO.toString()),
-                    bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
-                    parent = null).process()
-            assertThat(pojo.fields.find { it.name == "myField" }, notNullValue())
-            assertThat(pojo.fields.find { it.name == "baseField" }, notNullValue())
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun transient_ignore() {
-        singleRun("""
-            transient int foo;
-            int bar;
-        """) { pojo ->
-            assertThat(pojo.fields.size, `is`(1))
-            assertThat(pojo.fields[0].name, `is`("bar"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun transient_withColumnInfo() {
-        singleRun("""
-            @ColumnInfo
-            transient int foo;
-            int bar;
-        """) { pojo ->
-            assertThat(pojo.fields.map { it.name }.toSet(), `is`(setOf("bar", "foo")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun transient_embedded() {
-        singleRun("""
-            @Embedded
-            transient Foo foo;
-            int bar;
-            static class Foo {
-                int x;
-            }
-        """) { pojo ->
-            assertThat(pojo.fields.map { it.name }.toSet(), `is`(setOf("x", "bar")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun transient_insideEmbedded() {
-        singleRun("""
-            @Embedded
-            Foo foo;
-            int bar;
-            static class Foo {
-                transient int x;
-                int y;
-            }
-        """) { pojo ->
-            assertThat(pojo.fields.map { it.name }.toSet(), `is`(setOf("bar", "y")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun transient_relation() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                public transient List<User> user;
-                """, COMMON.USER
-        ) { pojo ->
-            assertThat(pojo.relations.size, `is`(1))
-            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
-            assertThat(pojo.relations.first().parentField.name, `is`("id"))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun embedded() {
-        singleRun(
-                """
-                int id;
-                @Embedded
-                Point myPoint;
-                static class Point {
-                    int x;
-                    int y;
-                }
-                """
-        ) { pojo ->
-            assertThat(pojo.fields.size, `is`(3))
-            assertThat(pojo.fields[1].name, `is`("x"))
-            assertThat(pojo.fields[2].name, `is`("y"))
-            assertThat(pojo.fields[0].parent, nullValue())
-            assertThat(pojo.fields[1].parent, notNullValue())
-            assertThat(pojo.fields[2].parent, notNullValue())
-            val parent = pojo.fields[2].parent!!
-            assertThat(parent.prefix, `is`(""))
-            assertThat(parent.field.name, `is`("myPoint"))
-            assertThat(parent.pojo.typeName,
-                    `is`(ClassName.get("foo.bar.MyPojo", "Point") as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun embeddedWithPrefix() {
-        singleRun(
-                """
-                int id;
-                @Embedded(prefix = "foo")
-                Point myPoint;
-                static class Point {
-                    int x;
-                    @ColumnInfo(name = "y2")
-                    int y;
-                }
-                """
-        ) { pojo ->
-            assertThat(pojo.fields.size, `is`(3))
-            assertThat(pojo.fields[1].name, `is`("x"))
-            assertThat(pojo.fields[2].name, `is`("y"))
-            assertThat(pojo.fields[1].columnName, `is`("foox"))
-            assertThat(pojo.fields[2].columnName, `is`("fooy2"))
-            val parent = pojo.fields[2].parent!!
-            assertThat(parent.prefix, `is`("foo"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun nestedEmbedded() {
-        singleRun(
-                """
-                int id;
-                @Embedded(prefix = "foo")
-                Point myPoint;
-                static class Point {
-                    int x;
-                    @ColumnInfo(name = "y2")
-                    int y;
-                    @Embedded(prefix = "bar")
-                    Coordinate coordinate;
-                }
-                static class Coordinate {
-                    double lat;
-                    double lng;
-                    @Ignore
-                    String ignored;
-                }
-                """
-        ) { pojo ->
-            assertThat(pojo.fields.size, `is`(5))
-            assertThat(pojo.fields.map { it.columnName }, `is`(
-                    listOf("id", "foox", "fooy2", "foobarlat", "foobarlng")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun duplicateColumnNames() {
-        singleRun(
-                """
-                int id;
-                @ColumnInfo(name = "id")
-                int another;
-                """
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.pojoDuplicateFieldNames("id", listOf("id", "another"))
-        ).and().withErrorContaining(
-                POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
-        ).and().withErrorCount(3)
-    }
-
-    @Test
-    fun duplicateColumnNamesFromEmbedded() {
-        singleRun(
-                """
-                int id;
-                @Embedded
-                Foo foo;
-                static class Foo {
-                    @ColumnInfo(name = "id")
-                    int x;
-                }
-                """
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.pojoDuplicateFieldNames("id", listOf("id", "foo > x"))
-        ).and().withErrorContaining(
-                POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
-        ).and().withErrorCount(3)
-    }
-
-    @Test
-    fun dropSubPrimaryKeyNoWarningForPojo() {
-        singleRun(
-                """
-                @PrimaryKey
-                int id;
-                @Embedded
-                Point myPoint;
-                static class Point {
-                    @PrimaryKey
-                    int x;
-                    int y;
-                }
-                """
-        ) { _ ->
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun relation_notCollection() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                public User user;
-                """, COMMON.USER
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(RELATION_NOT_COLLECTION)
-    }
-
-    @Test
-    fun relation_columnInfo() {
-        singleRun(
-                """
-                int id;
-                @ColumnInfo
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                public List<User> user;
-                """, COMMON.USER
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION)
-    }
-
-    @Test
-    fun relation_notEntity() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                public List<NotAnEntity> user;
-                """, COMMON.NOT_AN_ENTITY
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
-    }
-
-    @Test
-    fun relation_missingParent() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "idk", entityColumn = "uid")
-                public List<User> user;
-                """, COMMON.USER
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(
-                relationCannotFindParentEntityField("foo.bar.MyPojo", "idk", listOf("id"))
-        )
-    }
-
-    @Test
-    fun relation_missingEntityField() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "idk")
-                public List<User> user;
-                """, COMMON.USER
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(
-                relationCannotFindEntityField("foo.bar.User", "idk",
-                        listOf("uid", "name", "lastName", "age"))
-        )
-    }
-
-    @Test
-    fun relation_missingType() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                public List<User> user;
-                """
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(CANNOT_FIND_TYPE)
-    }
-
-    @Test
-    fun relation_nestedField() {
-        singleRun(
-                """
-                static class Nested {
-                    @ColumnInfo(name = "foo")
-                    public int id;
-                }
-                @Embedded
-                Nested nested;
-                @Relation(parentColumn = "foo", entityColumn = "uid")
-                public List<User> user;
-                """, COMMON.USER
-        ) { pojo ->
-            assertThat(pojo.relations.first().parentField.columnName, `is`("foo"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun relation_nestedRelation() {
-        singleRun(
-                """
-                static class UserWithNested {
-                    @Embedded
-                    public User user;
-                    @Relation(parentColumn = "uid", entityColumn = "uid")
-                    public List<User> selfs;
-                }
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid", entity = User.class)
-                public List<UserWithNested> user;
-                """, COMMON.USER
-        ) { pojo, _ ->
-            assertThat(pojo.relations.first().parentField.name, `is`("id"))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun relation_affinityMismatch() {
-        singleRun(
-                """
-                String id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                public List<User> user;
-                """, COMMON.USER
-        ) { pojo, invocation ->
-            // trigger assignment evaluation
-            RelationCollector.createCollectors(invocation.context, pojo.relations)
-            assertThat(pojo.relations.size, `is`(1))
-            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
-            assertThat(pojo.relations.first().parentField.name, `is`("id"))
-        }.compilesWithoutError().withWarningContaining(
-                ProcessorErrors.relationAffinityMismatch(
-                        parentAffinity = SQLTypeAffinity.TEXT,
-                        childAffinity = SQLTypeAffinity.INTEGER,
-                        parentColumn = "id",
-                        childColumn = "uid")
-        )
-    }
-
-    @Test
-    fun relation_simple() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                public List<User> user;
-                """, COMMON.USER
-        ) { pojo ->
-            assertThat(pojo.relations.size, `is`(1))
-            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
-            assertThat(pojo.relations.first().parentField.name, `is`("id"))
-        }.compilesWithoutError().withWarningCount(0)
-    }
-
-    @Test
-    fun relation_badProjection() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid", projection={"i_dont_exist"})
-                public List<User> user;
-                """, COMMON.USER
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.relationBadProject("foo.bar.User", listOf("i_dont_exist"),
-                        listOf("uid", "name", "lastName", "ageColumn"))
-        )
-    }
-
-    @Test
-    fun relation_badReturnTypeInGetter() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid")
-                private List<User> user;
-                public void setUser(List<User> user){ this.user = user;}
-                public User getUser(){return null;}
-                """, COMMON.USER
-        ) { _ ->
-        }.failsToCompile().withErrorContaining(CANNOT_FIND_GETTER_FOR_FIELD)
-    }
-
-    @Test
-    fun relation_primitiveList() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid",  projection={"uid"},
-                        entity = User.class)
-                public List<Integer> userIds;
-                """, COMMON.USER
-        ) { pojo ->
-            assertThat(pojo.relations.size, `is`(1))
-            val rel = pojo.relations.first()
-            assertThat(rel.projection, `is`(listOf("uid")))
-            assertThat(rel.entity.typeName, `is`(COMMON.USER_TYPE_NAME as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun relation_stringList() {
-        singleRun(
-                """
-                int id;
-                @Relation(parentColumn = "id", entityColumn = "uid",  projection={"name"},
-                        entity = User.class)
-                public List<String> userNames;
-                """, COMMON.USER
-        ) { pojo ->
-            assertThat(pojo.relations.size, `is`(1))
-            val rel = pojo.relations.first()
-            assertThat(rel.projection, `is`(listOf("name")))
-            assertThat(rel.entity.typeName, `is`(COMMON.USER_TYPE_NAME as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun cache() {
-        val pojo = """
-            $HEADER
-            int id;
-            $FOOTER
-            """.toJFO(MY_POJO.toString())
-        simpleRun(pojo) { invocation ->
-            val element = invocation.typeElement(MY_POJO.toString())
-            val pojo1 = PojoProcessor(invocation.context, element,
-                    FieldProcessor.BindingScope.BIND_TO_STMT, null).process()
-            assertThat(pojo1, notNullValue())
-            val pojo2 = PojoProcessor(invocation.context, element,
-                    FieldProcessor.BindingScope.BIND_TO_STMT, null).process()
-            assertThat(pojo2, sameInstance(pojo1))
-
-            val pojo3 = PojoProcessor(invocation.context, element,
-                    FieldProcessor.BindingScope.READ_FROM_CURSOR, null).process()
-            assertThat(pojo3, notNullValue())
-            assertThat(pojo3, not(sameInstance(pojo1)))
-
-            val pojo4 = PojoProcessor(invocation.context, element,
-                    FieldProcessor.BindingScope.TWO_WAY, null).process()
-            assertThat(pojo4, notNullValue())
-            assertThat(pojo4, not(sameInstance(pojo1)))
-            assertThat(pojo4, not(sameInstance(pojo3)))
-
-            val pojo5 = PojoProcessor(invocation.context, element,
-                    FieldProcessor.BindingScope.TWO_WAY, null).process()
-            assertThat(pojo5, sameInstance(pojo4))
-
-            val type = invocation.context.COMMON_TYPES.STRING
-            val mockElement = mock(Element::class.java)
-            doReturn(type).`when`(mockElement).asType()
-            val fakeField = Field(
-                    element = mockElement,
-                    name = "foo",
-                    type = type,
-                    affinity = SQLTypeAffinity.TEXT,
-                    columnName = "foo",
-                    parent = null,
-                    indexed = false
-            )
-            val fakeEmbedded = EmbeddedField(fakeField, "", null)
-
-            val pojo6 = PojoProcessor(invocation.context, element,
-                    FieldProcessor.BindingScope.TWO_WAY, fakeEmbedded).process()
-            assertThat(pojo6, notNullValue())
-            assertThat(pojo6, not(sameInstance(pojo1)))
-            assertThat(pojo6, not(sameInstance(pojo3)))
-            assertThat(pojo6, not(sameInstance(pojo4)))
-
-            val pojo7 = PojoProcessor(invocation.context, element,
-                    FieldProcessor.BindingScope.TWO_WAY, fakeEmbedded).process()
-            assertThat(pojo7, sameInstance(pojo6))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun constructor_empty() {
-        val pojoCode = """
-            public String mName;
-            """
-        singleRun(pojoCode) { pojo ->
-            assertThat(pojo.constructor, notNullValue())
-            assertThat(pojo.constructor?.params, `is`(emptyList<Constructor.Param>()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun constructor_ambiguous_twoFieldsExactMatch() {
-        val pojoCode = """
-            public String mName;
-            public String _name;
-            public MyPojo(String mName) {
-            }
-            """
-        singleRun(pojoCode) { pojo ->
-            val param = pojo.constructor?.params?.first()
-            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
-            assertThat((param as Constructor.FieldParam).field.name, `is`("mName"))
-            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
-                    `is`(CallType.CONSTRUCTOR))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun constructor_ambiguous_oneTypeMatches() {
-        val pojoCode = """
-            public String mName;
-            public int _name;
-            public MyPojo(String name) {
-            }
-            """
-        singleRun(pojoCode) { pojo ->
-            val param = pojo.constructor?.params?.first()
-            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
-            assertThat((param as Constructor.FieldParam).field.name, `is`("mName"))
-            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
-                    `is`(CallType.CONSTRUCTOR))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun constructor_ambiguous_twoFields() {
-        val pojo = """
-            String mName;
-            String _name;
-            public MyPojo(String name) {
-            }
-            """
-        singleRun(pojo) { _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.ambigiousConstructor(MY_POJO.toString(),
-                        "name", listOf("mName", "_name"))
-        )
-    }
-
-    @Test
-    fun constructor_noMatchBadType() {
-        singleRun("""
-            int foo;
-            public MyPojo(String foo) {
-            }
-        """) { _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
-    }
-
-    @Test
-    fun constructor_noMatch() {
-        singleRun("""
-            String mName;
-            String _name;
-            public MyPojo(String foo) {
-            }
-        """) { _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
-    }
-
-    @Test
-    fun constructor_noMatchMultiArg() {
-        singleRun("""
-            String mName;
-            int bar;
-            public MyPojo(String foo, String name) {
-            }
-        """) { _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
-    }
-
-    @Test
-    fun constructor_multipleMatching() {
-        singleRun("""
-            String mName;
-            String mLastName;
-            public MyPojo(String name) {
-            }
-            public MyPojo(String name, String lastName) {
-            }
-        """) { _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
-    }
-
-    @Test
-    fun constructor_multipleMatchingWithIgnored() {
-        singleRun("""
-            String mName;
-            String mLastName;
-            @Ignore
-            public MyPojo(String name) {
-            }
-            public MyPojo(String name, String lastName) {
-            }
-        """) { pojo ->
-            assertThat(pojo.constructor, notNullValue())
-            assertThat(pojo.constructor?.params?.size, `is`(2))
-            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
-                    `is`(CallType.CONSTRUCTOR))
-            assertThat(pojo.fields.find { it.name == "mLastName" }?.setter?.callType,
-                    `is`(CallType.CONSTRUCTOR))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun constructor_dontTryForBindToScope() {
-        singleRun("""
-            String mName;
-            String mLastName;
-        """) { _, invocation ->
-            val process2 = PojoProcessor(baseContext = invocation.context,
-                    element = invocation.typeElement(MY_POJO.toString()),
-                    bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
-                    parent = null).process()
-            assertThat(process2.constructor, nullValue())
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun constructor_bindForTwoWay() {
-        singleRun("""
-            String mName;
-            String mLastName;
-        """) { _, invocation ->
-            val process2 = PojoProcessor(baseContext = invocation.context,
-                    element = invocation.typeElement(MY_POJO.toString()),
-                    bindingScope = FieldProcessor.BindingScope.TWO_WAY,
-                    parent = null).process()
-            assertThat(process2.constructor, notNullValue())
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun constructor_multipleMatching_withNoArg() {
-        singleRun("""
-            String mName;
-            String mLastName;
-            public MyPojo() {
-            }
-            public MyPojo(String name, String lastName) {
-            }
-        """) { pojo ->
-            assertThat(pojo.constructor?.params?.size ?: -1, `is`(0))
-        }.compilesWithoutError().withWarningContaining(
-                ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS_CHOOSING_NO_ARG)
-    }
-
-    @Test // added for b/69562125
-    fun constructor_withNullabilityAnnotation() {
-        singleRun("""
-            String mName;
-            public MyPojo(@android.support.annotation.NonNull String name) {}
-            """) { pojo ->
-            val constructor = pojo.constructor
-            assertThat(constructor, notNullValue())
-            assertThat(constructor!!.params.size, `is`(1))
-        }.compilesWithoutError()
-    }
-
-    @Test // b/69118713 common mistake so we better provide a good explanation
-    fun constructor_relationParameter() {
-        singleRun("""
-            @Relation(entity = foo.bar.User.class, parentColumn = "uid", entityColumn="uid",
-            projection = "name")
-            public List<String> items;
-            public String uid;
-            public MyPojo(String uid, List<String> items) {
-            }
-            """, COMMON.USER) { _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RELATION_CANNOT_BE_CONSTRUCTOR_PARAMETER
-        )
-    }
-
-    @Test
-    fun recursion_1Level() {
-        singleRun(
-                """
-                @Embedded
-                MyPojo myPojo;
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo -> foo.bar.MyPojo"))
-    }
-
-    @Test
-    fun recursion_2Levels_relationToEmbed() {
-        singleRun(
-                """
-                int pojoId;
-
-                @Relation(parentColumn = "pojoId", entityColumn = "entityId")
-                List<MyEntity> myEntity;
-
-                @Entity
-                static class MyEntity {
-                    int entityId;
-
-                    @Embedded
-                    MyPojo myPojo;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo -> foo.bar.MyPojo.MyEntity -> foo.bar.MyPojo"))
-    }
-
-    @Test
-    fun recursion_2Levels_onlyEmbeds_pojoToEntity() {
-        singleRun(
-                """
-                @Embedded
-                A a;
-
-                @Entity
-                static class A {
-                    @Embedded
-                    MyPojo myPojo;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
-    }
-
-    @Test
-    fun recursion_2Levels_onlyEmbeds_onlyPojos() {
-        singleRun(
-                """
-                @Embedded
-                A a;
-                static class A {
-                    @Embedded
-                    MyPojo myPojo;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
-    }
-
-    @Test
-    fun recursion_3Levels() {
-        singleRun(
-                """
-                @Embedded
-                A a;
-                public static class A {
-                    @Embedded
-                    B b;
-                }
-                public static class B {
-                    @Embedded
-                    MyPojo myPojo;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo"))
-    }
-
-    @Test
-    fun recursion_1Level_1LevelDown() {
-        singleRun(
-                """
-                @Embedded
-                A a;
-                static class A {
-                    @Embedded
-                    B b;
-                }
-                static class B {
-                    @Embedded
-                    A a;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo.A"))
-    }
-
-    @Test
-    fun recursion_branchAtLevel0_afterBackTrack() {
-        singleRun(
-                """
-                @PrimaryKey
-                int id;
-                @Embedded
-                A a;
-                @Embedded
-                C c;
-                static class A {
-                    @Embedded
-                    B b;
-                }
-                static class B {
-                }
-                static class C {
-                    @Embedded
-                    MyPojo myPojo;
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo -> foo.bar.MyPojo.C -> foo.bar.MyPojo"))
-    }
-
-    @Test
-    fun recursion_branchAtLevel1_afterBackTrack() {
-        singleRun(
-                """
-                @PrimaryKey
-                int id;
-                @Embedded
-                A a;
-                static class A {
-                    @Embedded
-                    B b;
-                    @Embedded
-                    MyPojo myPojo;
-                }
-                static class B {
-                    @Embedded
-                    C c;
-                }
-                static class C {
-                }
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
-                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
-    }
-
-    fun singleRun(
-            code: String, vararg jfos: JavaFileObject, handler: (Pojo) -> Unit): CompileTester {
-        return singleRun(code, *jfos) { pojo, _ ->
-            handler(pojo)
-        }
-    }
-
-    fun singleRun(
-            code: String, vararg jfos: JavaFileObject,
-            handler: (Pojo, TestInvocation) -> Unit): CompileTester {
-        val pojoJFO = """
-                $HEADER
-                $code
-                $FOOTER
-                """.toJFO(MY_POJO.toString())
-        val all = (jfos.toList() + pojoJFO).toTypedArray()
-        return simpleRun(*all) { invocation ->
-            handler.invoke(
-                    PojoProcessor(baseContext = invocation.context,
-                            element = invocation.typeElement(MY_POJO.toString()),
-                            bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
-                            parent = null).process(),
-                    invocation
-            )
-        }
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt
deleted file mode 100644
index e7a9673..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/QueryMethodProcessorTest.kt
+++ /dev/null
@@ -1,817 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import COMMON
-import android.arch.persistence.room.ColumnInfo
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.LifecyclesTypeNames
-import android.arch.persistence.room.ext.PagingTypeNames
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.Table
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER
-import android.arch.persistence.room.solver.query.result.DataSourceFactoryQueryResultBinder
-import android.arch.persistence.room.solver.query.result.LiveDataQueryResultBinder
-import android.arch.persistence.room.solver.query.result.PojoRowAdapter
-import android.arch.persistence.room.solver.query.result.SingleEntityQueryResultAdapter
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.QueryMethod
-import android.arch.persistence.room.vo.Warning
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth.assertAbout
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeVariableName
-import createVerifierFromEntities
-import mockElementAndType
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.hasItem
-import org.hamcrest.CoreMatchers.instanceOf
-import org.hamcrest.CoreMatchers.not
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mockito
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.TypeKind.INT
-import javax.lang.model.type.TypeMirror
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-@RunWith(Parameterized::class)
-class QueryMethodProcessorTest(val enableVerification: Boolean) {
-    companion object {
-        const val DAO_PREFIX = """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-                @Dao
-                abstract class MyClass {
-                """
-        const val DAO_SUFFIX = "}"
-        val POJO: ClassName = ClassName.get("foo.bar", "MyClass.Pojo")
-        @Parameterized.Parameters(name = "enableDbVerification={0}")
-        @JvmStatic
-        fun getParams() = arrayOf(true, false)
-
-        fun createField(name: String, columnName: String? = null): Field {
-            val (element, type) = mockElementAndType()
-            return Field(
-                    element = element,
-                    name = name,
-                    type = type,
-                    columnName = columnName ?: name,
-                    affinity = null
-            )
-        }
-    }
-
-    @Test
-    fun testReadNoParams() {
-        singleQueryMethod(
-                """
-                @Query("SELECT * from User")
-                abstract public int[] foo();
-                """) { parsedQuery, _ ->
-            assertThat(parsedQuery.name, `is`("foo"))
-            assertThat(parsedQuery.parameters.size, `is`(0))
-            assertThat(parsedQuery.returnType.typeName(),
-                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testSingleParam() {
-        singleQueryMethod(
-                """
-                @Query("SELECT * from User where uid = :x")
-                abstract public long foo(int x);
-                """) { parsedQuery, invocation ->
-            assertThat(parsedQuery.name, `is`("foo"))
-            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.LONG))
-            assertThat(parsedQuery.parameters.size, `is`(1))
-            val param = parsedQuery.parameters.first()
-            assertThat(param.name, `is`("x"))
-            assertThat(param.sqlName, `is`("x"))
-            assertThat(param.type,
-                    `is`(invocation.processingEnv.typeUtils.getPrimitiveType(INT) as TypeMirror))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testVarArgs() {
-        singleQueryMethod(
-                """
-                @Query("SELECT * from User where uid in (:ids)")
-                abstract public long foo(int... ids);
-                """) { parsedQuery, invocation ->
-            assertThat(parsedQuery.name, `is`("foo"))
-            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.LONG))
-            assertThat(parsedQuery.parameters.size, `is`(1))
-            val param = parsedQuery.parameters.first()
-            assertThat(param.name, `is`("ids"))
-            assertThat(param.sqlName, `is`("ids"))
-            val types = invocation.processingEnv.typeUtils
-            assertThat(param.type,
-                    `is`(types.getArrayType(types.getPrimitiveType(INT)) as TypeMirror))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testParamBindingMatchingNoName() {
-        singleQueryMethod(
-                """
-                @Query("SELECT uid from User where uid = :id")
-                abstract public long getIdById(int id);
-                """) { parsedQuery, _ ->
-            val section = parsedQuery.query.bindSections.first()
-            val param = parsedQuery.parameters.firstOrNull()
-            assertThat(section, notNullValue())
-            assertThat(param, notNullValue())
-            assertThat(parsedQuery.sectionToParamMapping, `is`(listOf(Pair(section, param))))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testParamBindingMatchingSimpleBind() {
-        singleQueryMethod(
-                """
-                @Query("SELECT uid from User where uid = :id")
-                abstract public long getIdById(int id);
-                """) { parsedQuery, _ ->
-            val section = parsedQuery.query.bindSections.first()
-            val param = parsedQuery.parameters.firstOrNull()
-            assertThat(section, notNullValue())
-            assertThat(param, notNullValue())
-            assertThat(parsedQuery.sectionToParamMapping,
-                    `is`(listOf(Pair(section, param))))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testParamBindingTwoBindVarsIntoTheSameParameter() {
-        singleQueryMethod(
-                """
-                @Query("SELECT uid from User where uid = :id OR uid = :id")
-                abstract public long getIdById(int id);
-                """) { parsedQuery, _ ->
-            val section = parsedQuery.query.bindSections[0]
-            val section2 = parsedQuery.query.bindSections[1]
-            val param = parsedQuery.parameters.firstOrNull()
-            assertThat(section, notNullValue())
-            assertThat(section2, notNullValue())
-            assertThat(param, notNullValue())
-            assertThat(parsedQuery.sectionToParamMapping,
-                    `is`(listOf(Pair(section, param), Pair(section2, param))))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testMissingParameterForBinding() {
-        singleQueryMethod(
-                """
-                @Query("SELECT uid from User where uid = :id OR uid = :uid")
-                abstract public long getIdById(int id);
-                """) { parsedQuery, _ ->
-            val section = parsedQuery.query.bindSections[0]
-            val section2 = parsedQuery.query.bindSections[1]
-            val param = parsedQuery.parameters.firstOrNull()
-            assertThat(section, notNullValue())
-            assertThat(section2, notNullValue())
-            assertThat(param, notNullValue())
-            assertThat(parsedQuery.sectionToParamMapping,
-                    `is`(listOf(Pair(section, param), Pair(section2, null))))
-        }
-                .failsToCompile()
-                .withErrorContaining(
-                        ProcessorErrors.missingParameterForBindVariable(listOf(":uid")))
-    }
-
-    @Test
-    fun test2MissingParameterForBinding() {
-        singleQueryMethod(
-                """
-                @Query("SELECT uid from User where name = :bar AND uid = :id OR uid = :uid")
-                abstract public long getIdById(int id);
-                """) { parsedQuery, _ ->
-            val bar = parsedQuery.query.bindSections[0]
-            val id = parsedQuery.query.bindSections[1]
-            val uid = parsedQuery.query.bindSections[2]
-            val param = parsedQuery.parameters.firstOrNull()
-            assertThat(bar, notNullValue())
-            assertThat(id, notNullValue())
-            assertThat(uid, notNullValue())
-            assertThat(param, notNullValue())
-            assertThat(parsedQuery.sectionToParamMapping,
-                    `is`(listOf(Pair(bar, null), Pair(id, param), Pair(uid, null))))
-        }
-                .failsToCompile()
-                .withErrorContaining(
-                        ProcessorErrors.missingParameterForBindVariable(listOf(":bar", ":uid")))
-    }
-
-    @Test
-    fun testUnusedParameters() {
-        singleQueryMethod(
-                """
-                @Query("SELECT uid from User where name = :bar")
-                abstract public long getIdById(int bar, int whyNotUseMe);
-                """) { parsedQuery, _ ->
-            val bar = parsedQuery.query.bindSections[0]
-            val barParam = parsedQuery.parameters.firstOrNull()
-            assertThat(bar, notNullValue())
-            assertThat(barParam, notNullValue())
-            assertThat(parsedQuery.sectionToParamMapping,
-                    `is`(listOf(Pair(bar, barParam))))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.unusedQueryMethodParameter(listOf("whyNotUseMe")))
-    }
-
-    @Test
-    fun testNameWithUnderscore() {
-        singleQueryMethod(
-                """
-                @Query("select * from User where uid = :_blah")
-                abstract public long getSth(int _blah);
-                """
-        ) { _, _ -> }
-                .failsToCompile()
-                .withErrorContaining(ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
-    }
-
-    @Test
-    fun testGenericReturnType() {
-        singleQueryMethod(
-                """
-                @Query("select * from User")
-                abstract public <T> ${CommonTypeNames.LIST}<T> foo(int x);
-                """) { parsedQuery, _ ->
-            val expected: TypeName = ParameterizedTypeName.get(ClassName.get(List::class.java),
-                    TypeVariableName.get("T"))
-            assertThat(parsedQuery.returnType.typeName(), `is`(expected))
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
-    }
-
-    @Test
-    fun testBadQuery() {
-        singleQueryMethod(
-                """
-                @Query("select * from :1 :2")
-                abstract public long foo(int x);
-                """) { _, _ ->
-            // do nothing
-        }.failsToCompile()
-                .withErrorContaining("UNEXPECTED_CHAR=:")
-    }
-
-    @Test
-    fun testLiveDataWithWithClause() {
-        singleQueryMethod(
-                """
-                @Query("WITH RECURSIVE tempTable(n, fact) AS (SELECT 0, 1 UNION ALL SELECT n+1,"
-                + " (n+1)*fact FROM tempTable WHERE n < 9) SELECT fact FROM tempTable, User")
-                abstract public ${LifecyclesTypeNames.LIVE_DATA}<${CommonTypeNames.LIST}<Integer>>
-                getFactorialLiveData();
-                """) { parsedQuery, _ ->
-            assertThat(parsedQuery.query.tables, hasItem(Table("User", "User")))
-            assertThat(parsedQuery.query.tables,
-                    not(hasItem(Table("tempTable", "tempTable"))))
-            assertThat(parsedQuery.query.tables.size, `is`(1))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testLiveDataWithNothingToObserve() {
-        singleQueryMethod(
-                """
-                @Query("SELECT 1")
-                abstract public ${LifecyclesTypeNames.LIVE_DATA}<Integer> getOne();
-                """) { _, _ ->
-            // do nothing
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-    }
-
-    @Test
-    fun testLiveDataWithWithClauseAndNothingToObserve() {
-        singleQueryMethod(
-                """
-                @Query("WITH RECURSIVE tempTable(n, fact) AS (SELECT 0, 1 UNION ALL SELECT n+1,"
-                + " (n+1)*fact FROM tempTable WHERE n < 9) SELECT fact FROM tempTable")
-                abstract public ${LifecyclesTypeNames.LIVE_DATA}<${CommonTypeNames.LIST}<Integer>>
-                getFactorialLiveData();
-                """) { _, _ ->
-            // do nothing
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-    }
-
-    @Test
-    fun testBoundGeneric() {
-        singleQueryMethod(
-                """
-                static abstract class BaseModel<T> {
-                    @Query("select COUNT(*) from User")
-                    abstract public T getT();
-                }
-                @Dao
-                static abstract class ExtendingModel extends BaseModel<Integer> {
-                }
-                """) { parsedQuery, _ ->
-            assertThat(parsedQuery.returnType.typeName(),
-                    `is`(ClassName.get(Integer::class.java) as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testBoundGenericParameter() {
-        singleQueryMethod(
-                """
-                static abstract class BaseModel<T> {
-                    @Query("select COUNT(*) from User where :t")
-                    abstract public int getT(T t);
-                }
-                @Dao
-                static abstract class ExtendingModel extends BaseModel<Integer> {
-                }
-                """) { parsedQuery, invocation ->
-            assertThat(parsedQuery.parameters.first().type,
-                    `is`(invocation.processingEnv.elementUtils
-                            .getTypeElement("java.lang.Integer").asType()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testReadDeleteWithBadReturnType() {
-        singleQueryMethod(
-                """
-                @Query("DELETE from User where uid = :id")
-                abstract public float foo(int id);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
-        )
-    }
-
-    @Test
-    fun testSimpleDelete() {
-        singleQueryMethod(
-                """
-                @Query("DELETE from User where uid = :id")
-                abstract public int foo(int id);
-                """) { parsedQuery, _ ->
-            assertThat(parsedQuery.name, `is`("foo"))
-            assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.INT))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testVoidDeleteQuery() {
-        singleQueryMethod(
-                """
-                @Query("DELETE from User where uid = :id")
-                abstract public void foo(int id);
-                """) { parsedQuery, _ ->
-            assertThat(parsedQuery.name, `is`("foo"))
-            assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.VOID))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testVoidUpdateQuery() {
-        singleQueryMethod(
-                """
-                @Query("update user set name = :name")
-                abstract public void updateAllNames(String name);
-                """) { parsedQuery, invocation ->
-            assertThat(parsedQuery.name, `is`("updateAllNames"))
-            assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.VOID))
-            assertThat(parsedQuery.parameters.first().type.typeName(),
-                    `is`(invocation.context.COMMON_TYPES.STRING.typeName()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testLiveDataQuery() {
-        singleQueryMethod(
-                """
-                @Query("select name from user where uid = :id")
-                abstract ${LifecyclesTypeNames.LIVE_DATA}<String> nameLiveData(String id);
-                """
-        ) { parsedQuery, _ ->
-            assertThat(parsedQuery.returnType.typeName(),
-                    `is`(ParameterizedTypeName.get(LifecyclesTypeNames.LIVE_DATA,
-                            String::class.typeName()) as TypeName))
-            assertThat(parsedQuery.queryResultBinder,
-                    instanceOf(LiveDataQueryResultBinder::class.java))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testNonSelectLiveData() {
-        singleQueryMethod(
-                """
-                @Query("delete from user where uid = :id")
-                abstract ${LifecyclesTypeNames.LIVE_DATA}<Integer> deleteLiveData(String id);
-                """
-        ) { _, _ ->
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT)
-    }
-
-    @Test
-    fun testDataSourceFactoryQuery() {
-        singleQueryMethod(
-                """
-                @Query("select name from user")
-                abstract ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, String>
-                nameDataSourceFactory();
-                """
-        ) { parsedQuery, _ ->
-            assertThat(parsedQuery.returnType.typeName(),
-                    `is`(ParameterizedTypeName.get(PagingTypeNames.DATA_SOURCE_FACTORY,
-                            Integer::class.typeName(), String::class.typeName()) as TypeName))
-            assertThat(parsedQuery.queryResultBinder,
-                    instanceOf(DataSourceFactoryQueryResultBinder::class.java))
-            val tableNames =
-                    (parsedQuery.queryResultBinder as DataSourceFactoryQueryResultBinder)
-                            .positionalDataSourceQueryResultBinder.tableNames
-            assertEquals(setOf("user"), tableNames)
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testMultiTableDataSourceFactoryQuery() {
-        singleQueryMethod(
-                """
-                @Query("select name from User u LEFT OUTER JOIN Book b ON u.uid == b.uid")
-                abstract ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, String>
-                nameDataSourceFactory();
-                """
-        ) { parsedQuery, _ ->
-            assertThat(parsedQuery.returnType.typeName(),
-                    `is`(ParameterizedTypeName.get(PagingTypeNames.DATA_SOURCE_FACTORY,
-                            Integer::class.typeName(), String::class.typeName()) as TypeName))
-            assertThat(parsedQuery.queryResultBinder,
-                    instanceOf(DataSourceFactoryQueryResultBinder::class.java))
-            val tableNames =
-                    (parsedQuery.queryResultBinder as DataSourceFactoryQueryResultBinder)
-                            .positionalDataSourceQueryResultBinder.tableNames
-            assertEquals(setOf("User", "Book"), tableNames)
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun query_detectTransaction_delete() {
-        singleQueryMethod(
-                """
-                @Query("delete from user where uid = :id")
-                abstract int deleteUser(String id);
-                """
-        ) { method, _ ->
-            assertThat(method.inTransaction, `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun query_detectTransaction_update() {
-        singleQueryMethod(
-                """
-                @Query("UPDATE user set uid = :id + 1 where uid = :id")
-                abstract int incrementId(String id);
-                """
-        ) { method, _ ->
-            assertThat(method.inTransaction, `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun query_detectTransaction_select() {
-        singleQueryMethod(
-                """
-                @Query("select * from user")
-                abstract int loadUsers();
-                """
-        ) { method, _ ->
-            assertThat(method.inTransaction, `is`(false))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun query_detectTransaction_selectInTransaction() {
-        singleQueryMethod(
-                """
-                @Transaction
-                @Query("select * from user")
-                abstract int loadUsers();
-                """
-        ) { method, _ ->
-            assertThat(method.inTransaction, `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun skipVerification() {
-        singleQueryMethod(
-                """
-                @SkipQueryVerification
-                @Query("SELECT foo from User")
-                abstract public int[] foo();
-                """) { parsedQuery, _ ->
-            assertThat(parsedQuery.name, `is`("foo"))
-            assertThat(parsedQuery.parameters.size, `is`(0))
-            assertThat(parsedQuery.returnType.typeName(),
-                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun suppressWarnings() {
-        singleQueryMethod("""
-                @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
-                @Query("SELECT uid from User")
-                abstract public int[] foo();
-                """) { method, invocation ->
-            assertThat(QueryMethodProcessor(
-                    baseContext = invocation.context,
-                    containing = Mockito.mock(DeclaredType::class.java),
-                    executableElement = method.element,
-                    dbVerifier = null).context.logger.suppressedWarnings
-                    , `is`(setOf(Warning.CURSOR_MISMATCH)))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun pojo_renamedColumn() {
-        pojoTest("""
-                String name;
-                String lName;
-                """, listOf("name", "lastName as lName")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
-            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
-        }?.compilesWithoutError()?.withWarningCount(0)
-    }
-
-    @Test
-    fun pojo_exactMatch() {
-        pojoTest("""
-                String name;
-                String lastName;
-                """, listOf("name", "lastName")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
-            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
-        }?.compilesWithoutError()?.withWarningCount(0)
-    }
-
-    @Test
-    fun pojo_exactMatchWithStar() {
-        pojoTest("""
-            String name;
-            String lastName;
-            int uid;
-            @ColumnInfo(name = "ageColumn")
-            int age;
-        """, listOf("*")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
-            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
-        }?.compilesWithoutError()?.withWarningCount(0)
-    }
-
-    @Test
-    fun pojo_nonJavaName() {
-        pojoTest("""
-            @ColumnInfo(name = "MAX(ageColumn)")
-            int maxAge;
-            String name;
-            """, listOf("MAX(ageColumn)", "name")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
-            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
-        }?.compilesWithoutError()?.withWarningCount(0)
-    }
-
-    @Test
-    fun pojo_noMatchingFields() {
-        pojoTest("""
-                String nameX;
-                String lastNameX;
-                """, listOf("name", "lastName")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("name", "lastName")))
-            assertThat(adapter?.mapping?.unusedFields, `is`(adapter?.pojo?.fields))
-        }?.failsToCompile()
-                ?.withErrorContaining(CANNOT_FIND_QUERY_RESULT_ADAPTER)
-                ?.and()
-                ?.withWarningContaining(
-                        ProcessorErrors.cursorPojoMismatch(
-                                pojoTypeName = POJO,
-                                unusedColumns = listOf("name", "lastName"),
-                                unusedFields = listOf(createField("nameX"),
-                                        createField("lastNameX")),
-                                allColumns = listOf("name", "lastName"),
-                                allFields = listOf(
-                                        createField("nameX"),
-                                        createField("lastNameX")
-                                )
-                        )
-                )
-    }
-
-    @Test
-    fun pojo_badQuery() {
-        // do not report mismatch if query is broken
-        pojoTest("""
-            @ColumnInfo(name = "MAX(ageColumn)")
-            int maxAge;
-            String name;
-            """, listOf("MAX(age)", "name")) { _, _, _ ->
-        }?.failsToCompile()
-                ?.withErrorContaining("no such column: age")
-                ?.and()
-                ?.withErrorCount(1)
-                ?.withWarningCount(0)
-    }
-
-    @Test
-    fun pojo_tooManyColumns() {
-        pojoTest("""
-            String name;
-            String lastName;
-            """, listOf("uid", "name", "lastName")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("uid")))
-            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
-        }?.compilesWithoutError()?.withWarningContaining(
-                ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
-                        unusedColumns = listOf("uid"),
-                        unusedFields = emptyList(),
-                        allColumns = listOf("uid", "name", "lastName"),
-                        allFields = listOf(createField("name"), createField("lastName"))
-                ))
-    }
-
-    @Test
-    fun pojo_tooManyFields() {
-        pojoTest("""
-            String name;
-            String lastName;
-            """, listOf("lastName")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
-            assertThat(adapter?.mapping?.unusedFields, `is`(
-                    adapter?.pojo?.fields?.filter { it.name == "name" }
-            ))
-        }?.compilesWithoutError()?.withWarningContaining(
-                ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
-                        unusedColumns = emptyList(),
-                        unusedFields = listOf(createField("name")),
-                        allColumns = listOf("lastName"),
-                        allFields = listOf(createField("name"), createField("lastName"))
-                ))
-    }
-
-    @Test
-    fun pojo_missingNonNull() {
-        pojoTest("""
-            @NonNull
-            String name;
-            String lastName;
-            """, listOf("lastName")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
-            assertThat(adapter?.mapping?.unusedFields, `is`(
-                    adapter?.pojo?.fields?.filter { it.name == "name" }
-            ))
-        }?.failsToCompile()?.withWarningContaining(
-                ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
-                        unusedColumns = emptyList(),
-                        unusedFields = listOf(createField("name")),
-                        allColumns = listOf("lastName"),
-                        allFields = listOf(createField("name"), createField("lastName"))
-                ))?.and()?.withErrorContaining(
-                ProcessorErrors.pojoMissingNonNull(pojoTypeName = POJO,
-                        missingPojoFields = listOf("name"),
-                        allQueryColumns = listOf("lastName")))
-    }
-
-    @Test
-    fun pojo_tooManyFieldsAndColumns() {
-        pojoTest("""
-            String name;
-            String lastName;
-            """, listOf("uid", "name")) { adapter, _, _ ->
-            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("uid")))
-            assertThat(adapter?.mapping?.unusedFields, `is`(
-                    adapter?.pojo?.fields?.filter { it.name == "lastName" }
-            ))
-        }?.compilesWithoutError()?.withWarningContaining(
-                ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeName = POJO,
-                        unusedColumns = listOf("uid"),
-                        unusedFields = listOf(createField("lastName")),
-                        allColumns = listOf("uid", "name"),
-                        allFields = listOf(createField("name"), createField("lastName"))
-                ))
-    }
-
-    fun pojoTest(pojoFields: String, queryColumns: List<String>,
-                 handler: (PojoRowAdapter?, QueryMethod, TestInvocation) -> Unit): CompileTester? {
-        val assertion = singleQueryMethod(
-                """
-                static class Pojo {
-                    $pojoFields
-                }
-                @Query("SELECT ${queryColumns.joinToString(", ")} from User LIMIT 1")
-                abstract MyClass.Pojo getNameAndLastNames();
-                """
-        ) { parsedQuery, invocation ->
-            val adapter = parsedQuery.queryResultBinder.adapter
-            if (enableVerification) {
-                if (adapter is SingleEntityQueryResultAdapter) {
-                    handler(adapter.rowAdapter as? PojoRowAdapter, parsedQuery, invocation)
-                } else {
-                    handler(null, parsedQuery, invocation)
-                }
-            } else {
-                assertThat(adapter, nullValue())
-            }
-        }
-        if (enableVerification) {
-            return assertion
-        } else {
-            assertion.failsToCompile().withErrorContaining(CANNOT_FIND_QUERY_RESULT_ADAPTER)
-            return null
-        }
-    }
-
-    fun singleQueryMethod(vararg input: String,
-                          handler: (QueryMethod, TestInvocation) -> Unit):
-            CompileTester {
-        return assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
-                ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER, COMMON.BOOK))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
-                                Entity::class, PrimaryKey::class)
-                        .nextRunHandler { invocation ->
-                            val (owner, methods) = invocation.roundEnv
-                                    .getElementsAnnotatedWith(Dao::class.java)
-                                    .map {
-                                        Pair(it,
-                                                invocation.processingEnv.elementUtils
-                                                        .getAllMembers(MoreElements.asType(it))
-                                                        .filter {
-                                                            it.hasAnnotation(Query::class)
-                                                        }
-                                        )
-                                    }.filter { it.second.isNotEmpty() }.first()
-                            val verifier = if (enableVerification) {
-                                createVerifierFromEntities(invocation)
-                            } else {
-                                null
-                            }
-                            val parser = QueryMethodProcessor(
-                                    baseContext = invocation.context,
-                                    containing = MoreTypes.asDeclared(owner.asType()),
-                                    executableElement = MoreElements.asExecutable(methods.first()),
-                                    dbVerifier = verifier)
-                            val parsedQuery = parser.process()
-                            handler(parsedQuery, invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
deleted file mode 100644
index 0a9be4a..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/RawQueryMethodProcessorTest.kt
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright 2018 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.processor
-
-import COMMON
-import android.arch.persistence.room.ColumnInfo
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.RawQuery
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.PagingTypeNames
-import android.arch.persistence.room.ext.SupportDbTypeNames
-import android.arch.persistence.room.ext.hasAnnotation
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.RawQueryMethod
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-
-class RawQueryMethodProcessorTest {
-    @Test
-    fun supportRawQuery() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public int[] foo(SupportSQLiteQuery query);
-                """) { query, _ ->
-            assertThat(query.name, `is`("foo"))
-            assertThat(query.runtimeQueryParam, `is`(
-                    RawQueryMethod.RuntimeQueryParameter(
-                            paramName = "query",
-                            type = SupportDbTypeNames.QUERY
-                    )
-            ))
-            assertThat(query.returnType.typeName(),
-                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun stringRawQuery() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public int[] foo(String query);
-                """) { query, _ ->
-            assertThat(query.name, `is`("foo"))
-            assertThat(query.runtimeQueryParam, `is`(
-                    RawQueryMethod.RuntimeQueryParameter(
-                            paramName = "query",
-                            type = CommonTypeNames.STRING
-                    )
-            ))
-            assertThat(query.returnType.typeName(),
-                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun withObservedEntities() {
-        singleQueryMethod(
-                """
-                @RawQuery(observedEntities = User.class)
-                abstract public LiveData<User> foo(SupportSQLiteQuery query);
-                """) { query, _ ->
-            assertThat(query.name, `is`("foo"))
-            assertThat(query.runtimeQueryParam, `is`(
-                    RawQueryMethod.RuntimeQueryParameter(
-                            paramName = "query",
-                            type = SupportDbTypeNames.QUERY
-                    )
-            ))
-            assertThat(query.observedTableNames.size, `is`(1))
-            assertThat(query.observedTableNames, `is`(setOf("User")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun observableWithoutEntities() {
-        singleQueryMethod(
-                """
-                @RawQuery(observedEntities = {})
-                abstract public LiveData<User> foo(SupportSQLiteQuery query);
-                """) { query, _ ->
-            assertThat(query.name, `is`("foo"))
-            assertThat(query.runtimeQueryParam, `is`(
-                    RawQueryMethod.RuntimeQueryParameter(
-                            paramName = "query",
-                            type = SupportDbTypeNames.QUERY
-                    )
-            ))
-            assertThat(query.observedTableNames, `is`(emptySet()))
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-    }
-
-    @Test
-    fun observableWithoutEntities_dataSourceFactory() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, User> getOne();
-                """) { _, _ ->
-            // do nothing
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-    }
-
-    @Test
-    fun observableWithoutEntities_positionalDataSource() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne();
-                """) { _, _ ->
-            // do nothing
-        }.failsToCompile()
-                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
-    }
-
-    @Test
-    fun positionalDataSource() {
-        singleQueryMethod(
-                """
-                @RawQuery(observedEntities = {User.class})
-                abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne(
-                        SupportSQLiteQuery query);
-                """) { _, _ ->
-            // do nothing
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun pojo() {
-        val pojo: TypeName = ClassName.get("foo.bar.MyClass", "MyPojo")
-        singleQueryMethod(
-                """
-                public class MyPojo {
-                    public String foo;
-                    public String bar;
-                }
-
-                @RawQuery
-                abstract public MyPojo foo(SupportSQLiteQuery query);
-                """) { query, _ ->
-            assertThat(query.name, `is`("foo"))
-            assertThat(query.runtimeQueryParam, `is`(
-                    RawQueryMethod.RuntimeQueryParameter(
-                            paramName = "query",
-                            type = SupportDbTypeNames.QUERY
-                    )
-            ))
-            assertThat(query.returnType.typeName(), `is`(pojo))
-            assertThat(query.observedTableNames, `is`(emptySet()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun void() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public void foo(SupportSQLiteQuery query);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE
-        )
-    }
-
-    @Test
-    fun noArgs() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public int[] foo();
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RAW_QUERY_BAD_PARAMS
-        )
-    }
-
-    @Test
-    fun tooManyArgs() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public int[] foo(String query, String query2);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RAW_QUERY_BAD_PARAMS
-        )
-    }
-
-    @Test
-    fun varargs() {
-        singleQueryMethod(
-                """
-                @RawQuery
-                abstract public int[] foo(String... query);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.RAW_QUERY_BAD_PARAMS
-        )
-    }
-
-    @Test
-    fun observed_notAnEntity() {
-        singleQueryMethod(
-                """
-                @RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class})
-                abstract public int[] foo(String query);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.rawQueryBadEntity(COMMON.NOT_AN_ENTITY_TYPE_NAME)
-        )
-    }
-
-    @Test
-    fun observed_relationPojo() {
-        singleQueryMethod(
-                """
-                public static class MyPojo {
-                    public String foo;
-                    @Relation(
-                        parentColumn = "foo",
-                        entityColumn = "name"
-                    )
-                    public java.util.List<User> users;
-                }
-                @RawQuery(observedEntities = MyPojo.class)
-                abstract public int[] foo(String query);
-                """) { method, _ ->
-            assertThat(method.observedTableNames, `is`(setOf("User")))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun observed_embedded() {
-        singleQueryMethod(
-                """
-                public static class MyPojo {
-                    public String foo;
-                    @Embedded
-                    public User users;
-                }
-                @RawQuery(observedEntities = MyPojo.class)
-                abstract public int[] foo(String query);
-                """) { method, _ ->
-            assertThat(method.observedTableNames, `is`(setOf("User")))
-        }.compilesWithoutError()
-    }
-
-    private fun singleQueryMethod(
-            vararg input: String,
-            handler: (RawQueryMethod, TestInvocation) -> Unit
-    ): CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        DAO_PREFIX
-                                + input.joinToString("\n")
-                                + DAO_SUFFIX
-                ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
-                        COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE,
-                        COMMON.NOT_AN_ENTITY))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
-                                Entity::class, PrimaryKey::class, RawQuery::class)
-                        .nextRunHandler { invocation ->
-                            val (owner, methods) = invocation.roundEnv
-                                    .getElementsAnnotatedWith(Dao::class.java)
-                                    .map {
-                                        Pair(it,
-                                                invocation.processingEnv.elementUtils
-                                                        .getAllMembers(MoreElements.asType(it))
-                                                        .filter {
-                                                            it.hasAnnotation(RawQuery::class)
-                                                        }
-                                        )
-                                    }.first { it.second.isNotEmpty() }
-                            val parser = RawQueryMethodProcessor(
-                                    baseContext = invocation.context,
-                                    containing = MoreTypes.asDeclared(owner.asType()),
-                                    executableElement = MoreElements.asExecutable(methods.first()))
-                            val parsedQuery = parser.process()
-                            handler(parsedQuery, invocation)
-                            true
-                        }
-                        .build())
-    }
-
-    companion object {
-        private const val DAO_PREFIX = """
-                package foo.bar;
-                import android.support.annotation.NonNull;
-                import android.arch.persistence.room.*;
-                import android.arch.persistence.db.SupportSQLiteQuery;
-                import android.arch.lifecycle.LiveData;
-                @Dao
-                abstract class MyClass {
-                """
-        private const val DAO_SUFFIX = "}"
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessorTest.kt
deleted file mode 100644
index 7a332ea..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/ShortcutMethodProcessorTest.kt
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.ext.CommonTypeNames
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.ShortcutMethod
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-import kotlin.reflect.KClass
-
-/**
- * Base test class for shortcut methods.
- */
-abstract class ShortcutMethodProcessorTest<out T : ShortcutMethod>(
-        val annotation: KClass<out Annotation>) {
-    companion object {
-        const val DAO_PREFIX = """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                import java.util.*;
-                @Dao
-                abstract class MyClass {
-                """
-        const val DAO_SUFFIX = "}"
-        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
-        val BOOK_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Book")
-    }
-
-    @Test
-    fun noParams() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public void foo();
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("foo"))
-            assertThat(shortcut.parameters.size, `is`(0))
-            assertThat(shortcut.returnCount, `is`(false))
-        }.failsToCompile().withErrorContaining(noParamsError())
-    }
-
-    abstract fun noParamsError(): String
-
-    @Test
-    fun single() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public int foo(User user);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("foo"))
-            assertThat(shortcut.parameters.size, `is`(1))
-            val param = shortcut.parameters.first()
-            assertThat(param.type.typeName(), `is`(USER_TYPE_NAME))
-            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
-            assertThat(shortcut.entities.size, `is`(1))
-            assertThat(shortcut.entities["user"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.returnCount, `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun notAnEntity() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public void foo(NotAnEntity notValid);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("foo"))
-            assertThat(shortcut.parameters.size, `is`(1))
-            val param = shortcut.parameters.first()
-            assertThat(param.entityType, `is`(CoreMatchers.nullValue()))
-            assertThat(shortcut.entities.size, `is`(0))
-        }.failsToCompile().withErrorContaining(
-                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
-        )
-    }
-
-    @Test
-    fun two() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public void foo(User u1, User u2);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("foo"))
-
-            assertThat(shortcut.parameters.size, `is`(2))
-            shortcut.parameters.forEach {
-                assertThat(it.type.typeName(), `is`(USER_TYPE_NAME))
-                assertThat(it.entityType?.typeName(), `is`(USER_TYPE_NAME))
-            }
-            assertThat(shortcut.entities.size, `is`(2))
-            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.parameters.map { it.name },
-                    `is`(listOf("u1", "u2")))
-            assertThat(shortcut.returnCount, `is`(false))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun list() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public int users(List<User> users);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("users"))
-            assertThat(shortcut.parameters.size, `is`(1))
-            val param = shortcut.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ParameterizedTypeName.get(
-                            ClassName.get("java.util", "List"), USER_TYPE_NAME) as TypeName))
-            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
-            assertThat(shortcut.entities.size, `is`(1))
-            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.returnCount, `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun array() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public void users(User[] users);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("users"))
-            assertThat(shortcut.parameters.size, `is`(1))
-            val param = shortcut.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName))
-            assertThat(shortcut.entities.size, `is`(1))
-            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.returnCount, `is`(false))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun set() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public void modifyUsers(Set<User> users);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("modifyUsers"))
-            assertThat(shortcut.parameters.size, `is`(1))
-            val param = shortcut.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ParameterizedTypeName.get(ClassName.get("java.util", "Set")
-                            , COMMON.USER_TYPE_NAME) as TypeName))
-            assertThat(shortcut.entities.size, `is`(1))
-            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.returnCount, `is`(false))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun iterable() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public void modifyUsers(Iterable<User> users);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("modifyUsers"))
-            assertThat(shortcut.parameters.size, `is`(1))
-            val param = shortcut.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ParameterizedTypeName.get(ClassName.get("java.lang", "Iterable")
-                            , COMMON.USER_TYPE_NAME) as TypeName))
-            assertThat(shortcut.entities.size, `is`(1))
-            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.returnCount, `is`(false))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun customCollection() {
-        singleShortcutMethod(
-                """
-                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
-                @${annotation.java.canonicalName}
-                abstract public void modifyUsers(MyList<String, User> users);
-                """) { shortcut, _ ->
-            assertThat(shortcut.name, `is`("modifyUsers"))
-            assertThat(shortcut.parameters.size, `is`(1))
-            val param = shortcut.parameters.first()
-            assertThat(param.type.typeName(), `is`(
-                    ParameterizedTypeName.get(ClassName.get("foo.bar", "MyClass.MyList")
-                            , CommonTypeNames.STRING
-                            , COMMON.USER_TYPE_NAME) as TypeName))
-            assertThat(shortcut.entities.size, `is`(1))
-            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.returnCount, `is`(false))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun differentTypes() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public void foo(User u1, Book b1);
-                """) { shortcut, _ ->
-            assertThat(shortcut.parameters.size, `is`(2))
-            assertThat(shortcut.parameters[0].type.typeName().toString(),
-                    `is`("foo.bar.User"))
-            assertThat(shortcut.parameters[1].type.typeName().toString(),
-                    `is`("foo.bar.Book"))
-            assertThat(shortcut.parameters.map { it.name }, `is`(listOf("u1", "b1")))
-            assertThat(shortcut.returnCount, `is`(false))
-            assertThat(shortcut.entities.size, `is`(2))
-            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
-            assertThat(shortcut.entities["b1"]?.typeName, `is`(BOOK_TYPE_NAME))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun invalidReturnType() {
-        singleShortcutMethod(
-                """
-                @${annotation.java.canonicalName}
-                abstract public long foo(User user);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(invalidReturnTypeError())
-    }
-
-    abstract fun invalidReturnTypeError(): String
-
-    abstract fun process(baseContext: Context, containing: DeclaredType,
-                         executableElement: ExecutableElement): T
-
-    fun singleShortcutMethod(vararg input: String,
-                             handler: (T, TestInvocation) -> Unit):
-            CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
-                ), COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(annotation, Dao::class)
-                        .nextRunHandler { invocation ->
-                            val (owner, methods) = invocation.roundEnv
-                                    .getElementsAnnotatedWith(Dao::class.java)
-                                    .map {
-                                        Pair(it,
-                                                invocation.processingEnv.elementUtils
-                                                        .getAllMembers(MoreElements.asType(it))
-                                                        .filter {
-                                                            MoreElements.isAnnotationPresent(it,
-                                                                    annotation.java)
-                                                        }
-                                        )
-                                    }.filter { it.second.isNotEmpty() }.first()
-                            val processed = process(
-                                    baseContext = invocation.context,
-                                    containing = MoreTypes.asDeclared(owner.asType()),
-                                    executableElement = MoreElements.asExecutable(methods.first()))
-                            handler(processed, invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessorTest.kt
deleted file mode 100644
index a07cbe6..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/TransactionMethodProcessorTest.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Transaction
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.TransactionMethod
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-@RunWith(JUnit4::class)
-class TransactionMethodProcessorTest {
-
-    companion object {
-        const val DAO_PREFIX = """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                import java.util.*;
-                @Dao
-                abstract class MyClass {
-                """
-        const val DAO_SUFFIX = "}"
-    }
-
-    @Test
-    fun simple() {
-        singleTransactionMethod(
-                """
-                @Transaction
-                public String doInTransaction(int param) { return null; }
-                """) { transaction, _ ->
-            assertThat(transaction.name, `is`("doInTransaction"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun modifier_private() {
-        singleTransactionMethod(
-                """
-                @Transaction
-                private String doInTransaction(int param) { return null; }
-                """) { transaction, _ ->
-            assertThat(transaction.name, `is`("doInTransaction"))
-        }.failsToCompile().withErrorContaining(ProcessorErrors.TRANSACTION_METHOD_MODIFIERS)
-    }
-
-    @Test
-    fun modifier_final() {
-        singleTransactionMethod(
-                """
-                @Transaction
-                public final String doInTransaction(int param) { return null; }
-                """) { transaction, _ ->
-            assertThat(transaction.name, `is`("doInTransaction"))
-        }.failsToCompile().withErrorContaining(ProcessorErrors.TRANSACTION_METHOD_MODIFIERS)
-    }
-
-    private fun singleTransactionMethod(vararg input: String,
-                                handler: (TransactionMethod, TestInvocation) -> Unit):
-            CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        TransactionMethodProcessorTest.DAO_PREFIX + input.joinToString("\n") +
-                                TransactionMethodProcessorTest.DAO_SUFFIX
-                )))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(Transaction::class, Dao::class)
-                        .nextRunHandler { invocation ->
-                            val (owner, methods) = invocation.roundEnv
-                                    .getElementsAnnotatedWith(Dao::class.java)
-                                    .map {
-                                        Pair(it,
-                                                invocation.processingEnv.elementUtils
-                                                        .getAllMembers(MoreElements.asType(it))
-                                                        .filter {
-                                                            MoreElements.isAnnotationPresent(it,
-                                                                    Transaction::class.java)
-                                                        }
-                                        )
-                                    }.filter { it.second.isNotEmpty() }.first()
-                            val processor = TransactionMethodProcessor(
-                                    baseContext = invocation.context,
-                                    containing = MoreTypes.asDeclared(owner.asType()),
-                                    executableElement = MoreElements.asExecutable(methods.first()))
-                            val processed = processor.process()
-                            handler(processed, invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessorTest.kt
deleted file mode 100644
index 84ff0f1..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/processor/UpdateMethodProcessorTest.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.processor
-
-import android.arch.persistence.room.OnConflictStrategy
-import android.arch.persistence.room.Update
-import android.arch.persistence.room.processor.ProcessorErrors
-        .UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
-import android.arch.persistence.room.processor.ProcessorErrors.UPDATE_MISSING_PARAMS
-import android.arch.persistence.room.vo.UpdateMethod
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.type.DeclaredType
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-@RunWith(JUnit4::class)
-class UpdateMethodProcessorTest : ShortcutMethodProcessorTest<UpdateMethod>(Update::class) {
-    override fun invalidReturnTypeError(): String = UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
-
-    override fun noParamsError(): String = UPDATE_MISSING_PARAMS
-
-    override fun process(baseContext: Context, containing: DeclaredType,
-                         executableElement: ExecutableElement): UpdateMethod {
-        return UpdateMethodProcessor(baseContext, containing, executableElement).process()
-    }
-
-    @Test
-    fun goodConflict() {
-        singleShortcutMethod(
-                """
-                @Update(onConflict = OnConflictStrategy.REPLACE)
-                abstract public void foo(User user);
-                """) { shortcut, _ ->
-            assertThat(shortcut.onConflictStrategy, `is`(OnConflictStrategy.REPLACE))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun badConflict() {
-        singleShortcutMethod(
-                """
-                @Update(onConflict = -1)
-                abstract public void foo(User user);
-                """) { _, _ ->
-        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/BasicColumnTypeAdaptersTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/BasicColumnTypeAdaptersTest.kt
deleted file mode 100644
index 024099b..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/BasicColumnTypeAdaptersTest.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver
-
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.testing.TestInvocation
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.JavaFile
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import simpleRun
-import testCodeGenScope
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.type.PrimitiveType
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-
-@RunWith(Parameterized::class)
-class BasicColumnTypeAdaptersTest(val input: Input, val bindCode: String,
-                                  val cursorCode: String) {
-    val scope = testCodeGenScope()
-
-    companion object {
-        val SQLITE_STMT: TypeName = ClassName.get("android.database.sqlite", "SQLiteStatement")
-        val CURSOR: TypeName = ClassName.get("android.database", "Cursor")
-
-        @Parameterized.Parameters(name = "kind:{0},bind:_{1},cursor:_{2}")
-        @JvmStatic
-        fun params(): List<Array<Any>> {
-            return listOf(
-                    arrayOf(Input(TypeKind.INT),
-                            "st.bindLong(6, inp);",
-                            "out = crs.getInt(9);"),
-                    arrayOf(Input(TypeKind.BYTE),
-                            "st.bindLong(6, inp);",
-                            "out = (byte) crs.getShort(9);"),
-                    arrayOf(Input(TypeKind.SHORT),
-                            "st.bindLong(6, inp);",
-                            "out = crs.getShort(9);"),
-                    arrayOf(Input(TypeKind.LONG),
-                            "st.bindLong(6, inp);",
-                            "out = crs.getLong(9);"),
-                    arrayOf(Input(TypeKind.CHAR),
-                            "st.bindLong(6, inp);",
-                            "out = (char) crs.getInt(9);"),
-                    arrayOf(Input(TypeKind.FLOAT),
-                            "st.bindDouble(6, inp);",
-                            "out = crs.getFloat(9);"),
-                    arrayOf(Input(TypeKind.DOUBLE),
-                            "st.bindDouble(6, inp);",
-                            "out = crs.getDouble(9);"),
-                    arrayOf(Input(TypeKind.DECLARED, "java.lang.String"),
-                            """
-                            if (inp == null) {
-                              st.bindNull(6);
-                            } else {
-                              st.bindString(6, inp);
-                            }
-                            """.trimIndent(),
-                            "out = crs.getString(9);")
-            )
-        }
-    }
-
-    @Test
-    fun bind() {
-        simpleRun { invocation ->
-            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
-            adapter.bindToStmt("st", "6", "inp", scope)
-            assertThat(scope.generate().trim(), `is`(bindCode))
-            generateCode(invocation, false)
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun boxedBind() {
-        if (!input.typeKind.isPrimitive) {
-            return // no-op for those
-        }
-        simpleRun { invocation ->
-            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(
-                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
-            adapter.bindToStmt("st", "6", "inp", scope)
-            assertThat(scope.generate().trim(), `is`(
-                    """
-                    if (inp == null) {
-                      st.bindNull(6);
-                    } else {
-                      $bindCode
-                    }
-                    """.trimIndent()
-            ))
-            generateCode(invocation, true)
-        }.compilesWithoutError()
-    }
-
-    private fun generateCode(invocation: TestInvocation, boxed: Boolean) {
-        val typeMirror = if (boxed) input.getBoxedTypeMirror(invocation.processingEnv)
-        else input.getTypeMirror(invocation.processingEnv)
-        val spec = TypeSpec.classBuilder("OutClass")
-                .addField(FieldSpec.builder(SQLITE_STMT, "st").build())
-                .addField(FieldSpec.builder(CURSOR, "crs").build())
-                .addField(FieldSpec.builder(TypeName.get(typeMirror), "out").build())
-                .addField(FieldSpec.builder(TypeName.get(typeMirror), "inp").build())
-                .addMethod(
-                        MethodSpec.methodBuilder("foo")
-                                .addCode(scope.builder().build())
-                                .build()
-                )
-                .build()
-        JavaFile.builder("foo.bar", spec).build().writeTo(invocation.processingEnv.filer)
-    }
-
-    @Test
-    fun read() {
-        simpleRun { invocation ->
-            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
-            adapter.readFromCursor("out", "crs", "9", scope)
-            assertThat(scope.generate().trim(), `is`(cursorCode))
-            generateCode(invocation, false)
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun readBoxed() {
-        if (!input.typeKind.isPrimitive) {
-            return // no-op for those
-        }
-        simpleRun { invocation ->
-            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
-                    .findColumnTypeAdapter(
-                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
-            adapter.readFromCursor("out", "crs", "9", scope)
-            assertThat(scope.generate().trim(), `is`(
-                    """
-                    if (crs.isNull(9)) {
-                      out = null;
-                    } else {
-                      $cursorCode
-                    }
-                    """.trimIndent()
-            ))
-            generateCode(invocation, true)
-        }.compilesWithoutError()
-    }
-
-    data class Input(val typeKind: TypeKind, val qName: String? = null) {
-        fun getTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
-            return if (typeKind.isPrimitive) {
-                processingEnv.typeUtils.getPrimitiveType(typeKind)
-            } else {
-                processingEnv.elementUtils.getTypeElement(qName).asType()
-            }
-        }
-
-        fun getBoxedTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
-            return if (typeKind.isPrimitive) {
-                processingEnv.typeUtils
-                        .boxedClass(getTypeMirror(processingEnv) as PrimitiveType)
-                        .asType()
-            } else {
-                getTypeMirror(processingEnv)
-            }
-        }
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/CustomTypeConverterResolutionTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/CustomTypeConverterResolutionTest.kt
deleted file mode 100644
index ae635b2..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/CustomTypeConverterResolutionTest.kt
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("HasPlatformType")
-
-package android.arch.persistence.room.solver
-
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Database
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.RoomProcessor
-import android.arch.persistence.room.TypeConverter
-import android.arch.persistence.room.TypeConverters
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.ext.S
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.AnnotationSpec
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.FieldSpec
-import com.squareup.javapoet.MethodSpec
-import com.squareup.javapoet.ParameterSpec
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeSpec
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.lang.model.element.Modifier
-import javax.tools.JavaFileObject
-
-@RunWith(JUnit4::class)
-class CustomTypeConverterResolutionTest {
-    fun TypeSpec.toJFO(): JavaFileObject {
-        return JavaFileObjects.forSourceString("foo.bar.${this.name}",
-                "package foo.bar;\n" + toString())
-    }
-
-    companion object {
-        val ENTITY = ClassName.get("foo.bar", "MyEntity")
-        val DB = ClassName.get("foo.bar", "MyDb")
-        val DAO = ClassName.get("foo.bar", "MyDao")
-
-        val CUSTOM_TYPE = ClassName.get("foo.bar", "CustomType")
-        val CUSTOM_TYPE_JFO = JavaFileObjects.forSourceLines(CUSTOM_TYPE.toString(),
-                """
-                package ${CUSTOM_TYPE.packageName()};
-                public class ${CUSTOM_TYPE.simpleName()} {
-                    public int value;
-                }
-                """)
-        val CUSTOM_TYPE_CONVERTER = ClassName.get("foo.bar", "MyConverter")
-        val CUSTOM_TYPE_CONVERTER_JFO = JavaFileObjects.forSourceLines(
-                CUSTOM_TYPE_CONVERTER.toString(),
-                """
-                package ${CUSTOM_TYPE_CONVERTER.packageName()};
-                public class ${CUSTOM_TYPE_CONVERTER.simpleName()} {
-                    @${TypeConverter::class.java.canonicalName}
-                    public static $CUSTOM_TYPE toCustom(int value) {
-                        return null;
-                    }
-                    @${TypeConverter::class.java.canonicalName}
-                    public static int fromCustom($CUSTOM_TYPE input) {
-                        return 0;
-                    }
-                }
-                """)
-        val CUSTOM_TYPE_SET = ParameterizedTypeName.get(
-                ClassName.get(Set::class.java), CUSTOM_TYPE)
-        val CUSTOM_TYPE_SET_CONVERTER = ClassName.get("foo.bar", "MySetConverter")
-        val CUSTOM_TYPE_SET_CONVERTER_JFO = JavaFileObjects.forSourceLines(
-                CUSTOM_TYPE_SET_CONVERTER.toString(),
-                """
-                package ${CUSTOM_TYPE_SET_CONVERTER.packageName()};
-                import java.util.HashSet;
-                import java.util.Set;
-                public class ${CUSTOM_TYPE_SET_CONVERTER.simpleName()} {
-                    @${TypeConverter::class.java.canonicalName}
-                    public static $CUSTOM_TYPE_SET toCustom(int value) {
-                        return null;
-                    }
-                    @${TypeConverter::class.java.canonicalName}
-                    public static int fromCustom($CUSTOM_TYPE_SET input) {
-                        return 0;
-                    }
-                }
-                """)
-    }
-
-    @Test
-    fun useFromDatabase_forEntity() {
-        val entity = createEntity(hasCustomField = true)
-        val database = createDatabase(hasConverters = true, hasDao = true)
-        val dao = createDao(hasQueryReturningEntity = true, hasQueryWithCustomParam = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun collection_forEntity() {
-        val entity = createEntity(
-                hasCustomField = true,
-                useCollection = true)
-        val database = createDatabase(
-                hasConverters = true,
-                hasDao = true,
-                useCollection = true)
-        val dao = createDao(
-                hasQueryWithCustomParam = false,
-                useCollection = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun collection_forDao() {
-        val entity = createEntity(
-                hasCustomField = true,
-                useCollection = true)
-        val database = createDatabase(
-                hasConverters = true,
-                hasDao = true,
-                useCollection = true)
-        val dao = createDao(
-                hasQueryWithCustomParam = true,
-                useCollection = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun useFromDatabase_forQueryParameter() {
-        val entity = createEntity()
-        val database = createDatabase(hasConverters = true, hasDao = true)
-        val dao = createDao(hasQueryWithCustomParam = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun useFromDatabase_forReturnValue() {
-        val entity = createEntity(hasCustomField = true)
-        val database = createDatabase(hasConverters = true, hasDao = true)
-        val dao = createDao(hasQueryReturningEntity = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun useFromDao_forQueryParameter() {
-        val entity = createEntity()
-        val database = createDatabase(hasDao = true)
-        val dao = createDao(hasConverters = true, hasQueryReturningEntity = true,
-                hasQueryWithCustomParam = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun useFromEntity_forReturnValue() {
-        val entity = createEntity(hasCustomField = true, hasConverters = true)
-        val database = createDatabase(hasDao = true)
-        val dao = createDao(hasQueryReturningEntity = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun useFromEntityField_forReturnValue() {
-        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
-        val database = createDatabase(hasDao = true)
-        val dao = createDao(hasQueryReturningEntity = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun useFromEntity_forQueryParameter() {
-        val entity = createEntity(hasCustomField = true, hasConverters = true)
-        val database = createDatabase(hasDao = true)
-        val dao = createDao(hasQueryWithCustomParam = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO())
-                .failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
-    }
-
-    @Test
-    fun useFromEntityField_forQueryParameter() {
-        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
-        val database = createDatabase(hasDao = true)
-        val dao = createDao(hasQueryWithCustomParam = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO())
-                .failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
-    }
-
-    @Test
-    fun useFromQueryMethod_forQueryParameter() {
-        val entity = createEntity()
-        val database = createDatabase(hasDao = true)
-        val dao = createDao(hasQueryWithCustomParam = true, hasMethodConverters = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    @Test
-    fun useFromQueryParameter_forQueryParameter() {
-        val entity = createEntity()
-        val database = createDatabase(hasDao = true)
-        val dao = createDao(hasQueryWithCustomParam = true, hasParameterConverters = true)
-        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
-    }
-
-    fun run(vararg jfos: JavaFileObject): CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(jfos.toList() + CUSTOM_TYPE_JFO + CUSTOM_TYPE_CONVERTER_JFO
-                        + CUSTOM_TYPE_SET_CONVERTER_JFO)
-                .processedWith(RoomProcessor())
-    }
-
-    private fun createEntity(
-            hasCustomField: Boolean = false,
-            hasConverters: Boolean = false,
-            hasConverterOnField: Boolean = false,
-            useCollection: Boolean = false): TypeSpec {
-        if (hasConverterOnField && hasConverters) {
-            throw IllegalArgumentException("cannot have both converters")
-        }
-        val type = if (useCollection) {
-            CUSTOM_TYPE_SET
-        } else {
-            CUSTOM_TYPE
-        }
-        return TypeSpec.classBuilder(ENTITY).apply {
-            addAnnotation(Entity::class.java)
-            addModifiers(Modifier.PUBLIC)
-            if (hasCustomField) {
-                addField(FieldSpec.builder(type, "myCustomField", Modifier.PUBLIC).apply {
-                    if (hasConverterOnField) {
-                        addAnnotation(createConvertersAnnotation())
-                    }
-                }.build())
-            }
-            if (hasConverters) {
-                addAnnotation(createConvertersAnnotation())
-            }
-            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
-                addAnnotation(PrimaryKey::class.java)
-            }.build())
-        }.build()
-    }
-
-    private fun createDatabase(
-            hasConverters: Boolean = false,
-            hasDao: Boolean = false,
-            useCollection: Boolean = false): TypeSpec {
-        return TypeSpec.classBuilder(DB).apply {
-            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
-            superclass(RoomTypeNames.ROOM_DB)
-            if (hasConverters) {
-                addAnnotation(createConvertersAnnotation(useCollection = useCollection))
-            }
-            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
-                addAnnotation(PrimaryKey::class.java)
-            }.build())
-            if (hasDao) {
-                addMethod(MethodSpec.methodBuilder("getDao").apply {
-                    addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
-                    returns(DAO)
-                }.build())
-            }
-            addAnnotation(
-                    AnnotationSpec.builder(Database::class.java).apply {
-                        addMember("entities", "{$T.class}", ENTITY)
-                        addMember("version", "42")
-                    }.build()
-            )
-        }.build()
-    }
-
-    private fun createDao(
-            hasConverters: Boolean = false,
-            hasQueryReturningEntity: Boolean = false,
-            hasQueryWithCustomParam: Boolean = false,
-            hasMethodConverters: Boolean = false,
-            hasParameterConverters: Boolean = false,
-            useCollection: Boolean = false): TypeSpec {
-        val annotationCount = listOf(hasMethodConverters, hasConverters, hasParameterConverters)
-                .map { if (it) 1 else 0 }.sum()
-        if (annotationCount > 1) {
-            throw IllegalArgumentException("cannot set both of these")
-        }
-        if (hasParameterConverters && !hasQueryWithCustomParam) {
-            throw IllegalArgumentException("inconsistent")
-        }
-        return TypeSpec.classBuilder(DAO).apply {
-            addAnnotation(Dao::class.java)
-            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
-            if (hasConverters) {
-                addAnnotation(createConvertersAnnotation(useCollection = useCollection))
-            }
-            if (hasQueryReturningEntity) {
-                addMethod(MethodSpec.methodBuilder("loadAll").apply {
-                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
-                        addMember("value", S, "SELECT * FROM ${ENTITY.simpleName()} LIMIT 1")
-                    }.build())
-                    addModifiers(Modifier.ABSTRACT)
-                    returns(ENTITY)
-                }.build())
-            }
-            val customType = if (useCollection) {
-                CUSTOM_TYPE_SET
-            } else {
-                CUSTOM_TYPE
-            }
-            if (hasQueryWithCustomParam) {
-                addMethod(MethodSpec.methodBuilder("queryWithCustom").apply {
-                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
-                        addMember("value", S, "SELECT COUNT(*) FROM ${ENTITY.simpleName()} where" +
-                                " id = :custom")
-                    }.build())
-                    if (hasMethodConverters) {
-                        addAnnotation(createConvertersAnnotation(useCollection = useCollection))
-                    }
-                    addParameter(ParameterSpec.builder(customType, "custom").apply {
-                        if (hasParameterConverters) {
-                            addAnnotation(createConvertersAnnotation(useCollection = useCollection))
-                        }
-                    }.build())
-                    addModifiers(Modifier.ABSTRACT)
-                    returns(TypeName.INT)
-                }.build())
-            }
-        }.build()
-    }
-
-    private fun createConvertersAnnotation(useCollection: Boolean = false): AnnotationSpec {
-        val converter = if (useCollection) {
-            CUSTOM_TYPE_SET_CONVERTER
-        } else {
-            CUSTOM_TYPE_CONVERTER
-        }
-        return AnnotationSpec.builder(TypeConverters::class.java)
-                .addMember("value", "$T.class", converter).build()
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAdapterStoreTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAdapterStoreTest.kt
deleted file mode 100644
index 7ad370e..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAdapterStoreTest.kt
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver
-
-import COMMON
-import android.arch.paging.DataSource
-import android.arch.paging.PositionalDataSource
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.ext.L
-import android.arch.persistence.room.ext.LifecyclesTypeNames
-import android.arch.persistence.room.ext.PagingTypeNames
-import android.arch.persistence.room.ext.ReactiveStreamsTypeNames
-import android.arch.persistence.room.ext.RoomTypeNames.STRING_UTIL
-import android.arch.persistence.room.ext.RxJava2TypeNames
-import android.arch.persistence.room.ext.T
-import android.arch.persistence.room.ext.typeName
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.processor.ProcessorErrors
-import android.arch.persistence.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.DataSourceQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.FlowableQueryResultBinderProvider
-import android.arch.persistence.room.solver.binderprovider.LiveDataQueryResultBinderProvider
-import android.arch.persistence.room.solver.types.CompositeAdapter
-import android.arch.persistence.room.solver.types.TypeConverter
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.instanceOf
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import simpleRun
-import testCodeGenScope
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.type.TypeKind
-
-@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-@RunWith(JUnit4::class)
-class TypeAdapterStoreTest {
-    companion object {
-        fun tmp(index: Int) = CodeGenScope._tmpVar(index)
-    }
-
-    @Test
-    fun testDirect() {
-        singleRun { invocation ->
-            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
-            val primitiveType = invocation.processingEnv.typeUtils.getPrimitiveType(TypeKind.INT)
-            val adapter = store.findColumnTypeAdapter(primitiveType, null)
-            assertThat(adapter, notNullValue())
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testJavaLangBoolean() {
-        singleRun { invocation ->
-            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
-            val boolean = invocation
-                    .processingEnv
-                    .elementUtils
-                    .getTypeElement("java.lang.Boolean")
-                    .asType()
-            val adapter = store.findColumnTypeAdapter(boolean, null)
-            assertThat(adapter, notNullValue())
-            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
-            val composite = adapter as CompositeAdapter
-            assertThat(composite.intoStatementConverter?.from?.typeName(),
-                    `is`(TypeName.BOOLEAN.box()))
-            assertThat(composite.columnTypeAdapter.out.typeName(),
-                    `is`(TypeName.INT.box()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testVia1TypeAdapter() {
-        singleRun { invocation ->
-            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
-            val booleanType = invocation.processingEnv.typeUtils
-                    .getPrimitiveType(TypeKind.BOOLEAN)
-            val adapter = store.findColumnTypeAdapter(booleanType, null)
-            assertThat(adapter, notNullValue())
-            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
-            val bindScope = testCodeGenScope()
-            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
-            assertThat(bindScope.generate().trim(), `is`("""
-                    final int ${tmp(0)};
-                    ${tmp(0)} = fooVar ? 1 : 0;
-                    stmt.bindLong(41, ${tmp(0)});
-                    """.trimIndent()))
-
-            val cursorScope = testCodeGenScope()
-            adapter.readFromCursor("res", "curs", "7", cursorScope)
-            assertThat(cursorScope.generate().trim(), `is`("""
-                    final int ${tmp(0)};
-                    ${tmp(0)} = curs.getInt(7);
-                    res = ${tmp(0)} != 0;
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testVia2TypeAdapters() {
-        singleRun { invocation ->
-            val store = TypeAdapterStore.create(Context(invocation.processingEnv),
-                    pointTypeConverters(invocation.processingEnv))
-            val pointType = invocation.processingEnv.elementUtils
-                    .getTypeElement("foo.bar.Point").asType()
-            val adapter = store.findColumnTypeAdapter(pointType, null)
-            assertThat(adapter, notNullValue())
-            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
-
-            val bindScope = testCodeGenScope()
-            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
-            assertThat(bindScope.generate().trim(), `is`("""
-                    final int ${tmp(0)};
-                    final boolean ${tmp(1)};
-                    ${tmp(1)} = foo.bar.Point.toBoolean(fooVar);
-                    ${tmp(0)} = ${tmp(1)} ? 1 : 0;
-                    stmt.bindLong(41, ${tmp(0)});
-                    """.trimIndent()))
-
-            val cursorScope = testCodeGenScope()
-            adapter.readFromCursor("res", "curs", "11", cursorScope).toString()
-            assertThat(cursorScope.generate().trim(), `is`("""
-                    final int ${tmp(0)};
-                    ${tmp(0)} = curs.getInt(11);
-                    final boolean ${tmp(1)};
-                    ${tmp(1)} = ${tmp(0)} != 0;
-                    res = foo.bar.Point.fromBoolean(${tmp(1)});
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testDate() {
-        singleRun { (processingEnv) ->
-            val store = TypeAdapterStore.create(Context(processingEnv),
-                    dateTypeConverters(processingEnv))
-            val tDate = processingEnv.elementUtils.getTypeElement("java.util.Date").asType()
-            val adapter = store.findCursorValueReader(tDate, SQLTypeAffinity.INTEGER)
-            assertThat(adapter, notNullValue())
-            assertThat(adapter?.typeMirror(), `is`(tDate))
-            val bindScope = testCodeGenScope()
-            adapter!!.readFromCursor("outDate", "curs", "0", bindScope)
-            assertThat(bindScope.generate().trim(), `is`("""
-                final java.lang.Long _tmp;
-                if (curs.isNull(0)) {
-                  _tmp = null;
-                } else {
-                  _tmp = curs.getLong(0);
-                }
-                // convert Long to Date;
-            """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testIntList() {
-        singleRun { invocation ->
-            val binders = createIntListToStringBinders(invocation)
-            val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0],
-                    binders[1])
-
-            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
-            assertThat(adapter, notNullValue())
-
-            val bindScope = testCodeGenScope()
-            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
-            assertThat(bindScope.generate().trim(), `is`("""
-                final java.lang.String ${tmp(0)};
-                ${tmp(0)} = android.arch.persistence.room.util.StringUtil.joinIntoString(fooVar);
-                if (${tmp(0)} == null) {
-                  stmt.bindNull(41);
-                } else {
-                  stmt.bindString(41, ${tmp(0)});
-                }
-                """.trimIndent()))
-
-            val converter = store.findTypeConverter(binders[0].from,
-                    invocation.context.COMMON_TYPES.STRING)
-            assertThat(converter, notNullValue())
-            assertThat(store.reverse(converter!!), `is`(binders[1]))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testOneWayConversion() {
-        singleRun { invocation ->
-            val binders = createIntListToStringBinders(invocation)
-            val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0])
-            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
-            assertThat(adapter, nullValue())
-
-            val stmtBinder = store.findStatementValueBinder(binders[0].from, null)
-            assertThat(stmtBinder, notNullValue())
-
-            val converter = store.findTypeConverter(binders[0].from,
-                    invocation.context.COMMON_TYPES.STRING)
-            assertThat(converter, notNullValue())
-            assertThat(store.reverse(converter!!), nullValue())
-        }
-    }
-
-    @Test
-    fun testMissingRxRoom() {
-        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE)) { invocation ->
-            val publisherElement = invocation.processingEnv.elementUtils
-                    .getTypeElement(ReactiveStreamsTypeNames.PUBLISHER.toString())
-            assertThat(publisherElement, notNullValue())
-            assertThat(FlowableQueryResultBinderProvider(invocation.context).matches(
-                    MoreTypes.asDeclared(publisherElement.asType())), `is`(true))
-        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
-    }
-
-    @Test
-    fun testFindPublisher() {
-        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE, COMMON.RX2_ROOM)) {
-            invocation ->
-            val publisher = invocation.processingEnv.elementUtils
-                    .getTypeElement(ReactiveStreamsTypeNames.PUBLISHER.toString())
-            assertThat(publisher, notNullValue())
-            assertThat(FlowableQueryResultBinderProvider(invocation.context).matches(
-                    MoreTypes.asDeclared(publisher.asType())), `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testFindFlowable() {
-        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE, COMMON.RX2_ROOM)) {
-            invocation ->
-            val flowable = invocation.processingEnv.elementUtils
-                    .getTypeElement(RxJava2TypeNames.FLOWABLE.toString())
-            assertThat(flowable, notNullValue())
-            assertThat(FlowableQueryResultBinderProvider(invocation.context).matches(
-                    MoreTypes.asDeclared(flowable.asType())), `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testFindLiveData() {
-        simpleRun(jfos = *arrayOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
-            invocation ->
-            val liveData = invocation.processingEnv.elementUtils
-                    .getTypeElement(LifecyclesTypeNames.LIVE_DATA.toString())
-            assertThat(liveData, notNullValue())
-            assertThat(LiveDataQueryResultBinderProvider(invocation.context).matches(
-                    MoreTypes.asDeclared(liveData.asType())), `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun findDataSource() {
-        simpleRun {
-            invocation ->
-            val dataSource = invocation.processingEnv.elementUtils
-                    .getTypeElement(DataSource::class.java.canonicalName)
-            assertThat(dataSource, notNullValue())
-            assertThat(DataSourceQueryResultBinderProvider(invocation.context).matches(
-                    MoreTypes.asDeclared(dataSource.asType())), `is`(true))
-        }.failsToCompile().withErrorContaining(ProcessorErrors.PAGING_SPECIFY_DATA_SOURCE_TYPE)
-    }
-
-    @Test
-    fun findPositionalDataSource() {
-        simpleRun {
-            invocation ->
-            val dataSource = invocation.processingEnv.elementUtils
-                    .getTypeElement(PositionalDataSource::class.java.canonicalName)
-            assertThat(dataSource, notNullValue())
-            assertThat(DataSourceQueryResultBinderProvider(invocation.context).matches(
-                    MoreTypes.asDeclared(dataSource.asType())), `is`(true))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun findDataSourceFactory() {
-        simpleRun(jfos = COMMON.DATA_SOURCE_FACTORY) {
-            invocation ->
-            val pagedListProvider = invocation.processingEnv.elementUtils
-                    .getTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY.toString())
-            assertThat(pagedListProvider, notNullValue())
-            assertThat(DataSourceFactoryQueryResultBinderProvider(invocation.context).matches(
-                    MoreTypes.asDeclared(pagedListProvider.asType())), `is`(true))
-        }.compilesWithoutError()
-    }
-
-    private fun createIntListToStringBinders(invocation: TestInvocation): List<TypeConverter> {
-        val intType = invocation.processingEnv.elementUtils
-                .getTypeElement(Integer::class.java.canonicalName)
-                .asType()
-        val listType = invocation.processingEnv.elementUtils
-                .getTypeElement(java.util.List::class.java.canonicalName)
-        val listOfInts = invocation.processingEnv.typeUtils.getDeclaredType(listType, intType)
-
-        val intListConverter = object : TypeConverter(listOfInts,
-                invocation.context.COMMON_TYPES.STRING) {
-            override fun convert(inputVarName: String, outputVarName: String,
-                                 scope: CodeGenScope) {
-                scope.builder().apply {
-                    addStatement("$L = $T.joinIntoString($L)", outputVarName, STRING_UTIL,
-                            inputVarName)
-                }
-            }
-        }
-
-        val stringToIntListConverter = object : TypeConverter(
-                invocation.context.COMMON_TYPES.STRING, listOfInts) {
-            override fun convert(inputVarName: String, outputVarName: String,
-                                 scope: CodeGenScope) {
-                scope.builder().apply {
-                    addStatement("$L = $T.splitToIntList($L)", outputVarName, STRING_UTIL,
-                            inputVarName)
-                }
-            }
-        }
-        return listOf(intListConverter, stringToIntListConverter)
-    }
-
-    fun singleRun(handler: (TestInvocation) -> Unit): CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.DummyClass",
-                        """
-                        package foo.bar;
-                        import android.arch.persistence.room.*;
-                        @Entity
-                        public class DummyClass {}
-                        """
-                ), JavaFileObjects.forSourceString("foo.bar.Point",
-                        """
-                        package foo.bar;
-                        import android.arch.persistence.room.*;
-                        @Entity
-                        public class Point {
-                            public int x, y;
-                            public Point(int x, int y) {
-                                this.x = x;
-                                this.y = y;
-                            }
-                            public static Point fromBoolean(boolean val) {
-                                return val ? new Point(1, 1) : new Point(0, 0);
-                            }
-                            public static boolean toBoolean(Point point) {
-                                return point.x > 0;
-                            }
-                        }
-                        """
-                )))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(Entity::class)
-                        .nextRunHandler { invocation ->
-                            handler(invocation)
-                            true
-                        }
-                        .build())
-    }
-
-    fun pointTypeConverters(env: ProcessingEnvironment): List<TypeConverter> {
-        val tPoint = env.elementUtils.getTypeElement("foo.bar.Point").asType()
-        val tBoolean = env.typeUtils.getPrimitiveType(TypeKind.BOOLEAN)
-        return listOf(
-                object : TypeConverter(tPoint, tBoolean) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().apply {
-                            addStatement("$L = $T.toBoolean($L)", outputVarName, from, inputVarName)
-                        }
-                    }
-                },
-                object : TypeConverter(tBoolean, tPoint) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().apply {
-                            addStatement("$L = $T.fromBoolean($L)", outputVarName, tPoint,
-                                    inputVarName)
-                        }
-                    }
-                }
-        )
-    }
-
-    fun dateTypeConverters(env: ProcessingEnvironment): List<TypeConverter> {
-        val tDate = env.elementUtils.getTypeElement("java.util.Date").asType()
-        val tLong = env.elementUtils.getTypeElement("java.lang.Long").asType()
-        return listOf(
-                object : TypeConverter(tDate, tLong) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().apply {
-                            addStatement("// convert Date to Long")
-                        }
-                    }
-                },
-                object : TypeConverter(tLong, tDate) {
-                    override fun convert(inputVarName: String, outputVarName: String,
-                                         scope: CodeGenScope) {
-                        scope.builder().apply {
-                            addStatement("// convert Long to Date")
-                        }
-                    }
-                }
-        )
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAssignmentTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAssignmentTest.kt
deleted file mode 100644
index 03d4031..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/TypeAssignmentTest.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 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.solver
-
-import android.arch.persistence.room.ext.getAllFieldsIncludingPrivateSupers
-import android.arch.persistence.room.ext.isAssignableWithoutVariance
-import android.arch.persistence.room.testing.TestInvocation
-import com.google.testing.compile.JavaFileObjects
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import simpleRun
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.TypeElement
-import javax.lang.model.element.VariableElement
-
-class TypeAssignmentTest {
-    companion object {
-        private val TEST_OBJECT = JavaFileObjects.forSourceString("foo.bar.MyObject",
-                """
-            package foo.bar;
-            import java.util.Set;
-            import java.util.HashSet;
-            import java.util.Map;
-            class MyObject {
-                String mString;
-                Integer mInteger;
-                Set<MyObject> mSet;
-                Set<? extends MyObject> mVarianceSet;
-                HashSet<MyObject> mHashSet;
-                Map<String, ?> mUnboundedMap;
-                Map<String, String> mStringMap;
-            }
-            """.trimIndent())
-    }
-
-    @Test
-    fun basic() {
-        runTest {
-            val testObject = typeElement("foo.bar.MyObject")
-            val string = testObject.getField(processingEnv, "mString")
-            val integer = testObject.getField(processingEnv, "mInteger")
-            assertThat(typeUtils.isAssignableWithoutVariance(string.asType(),
-                    integer.asType()),
-                    `is`(false))
-        }
-    }
-
-    @Test
-    fun generics() {
-        runTest {
-            val testObject = typeElement("foo.bar.MyObject")
-            val set = testObject.getField(processingEnv, "mSet").asType()
-            val hashSet = testObject.getField(processingEnv, "mHashSet").asType()
-            assertThat(typeUtils.isAssignableWithoutVariance(
-                    from = set,
-                    to = hashSet
-            ), `is`(false))
-            assertThat(typeUtils.isAssignableWithoutVariance(
-                    from = hashSet,
-                    to = set
-            ), `is`(true))
-        }
-    }
-
-    @Test
-    fun variance() {
-        /**
-         *  Set<User> userSet = null;
-         *  Set<? extends User> userSet2 = null;
-         *  userSet = userSet2;  // NOT OK for java but kotlin data classes hit this so we want
-         *                       // to accept it
-         */
-        runTest {
-            val testObject = typeElement("foo.bar.MyObject")
-            val set = testObject.getField(processingEnv, "mSet").asType()
-            val varianceSet = testObject.getField(processingEnv, "mVarianceSet").asType()
-            assertThat(typeUtils.isAssignableWithoutVariance(
-                    from = set,
-                    to = varianceSet
-            ), `is`(true))
-            assertThat(typeUtils.isAssignableWithoutVariance(
-                    from = varianceSet,
-                    to = set
-            ), `is`(true))
-        }
-    }
-
-    @Test
-    fun unboundedVariance() {
-        runTest {
-            val testObject = typeElement("foo.bar.MyObject")
-            val unbounded = testObject.getField(processingEnv, "mUnboundedMap").asType()
-            val objectMap = testObject.getField(processingEnv, "mStringMap").asType()
-            assertThat(typeUtils.isAssignableWithoutVariance(
-                    from = unbounded,
-                    to = objectMap
-            ), `is`(false))
-            assertThat(typeUtils.isAssignableWithoutVariance(
-                    from = objectMap,
-                    to = unbounded
-            ), `is`(true))
-        }
-    }
-
-    private fun TypeElement.getField(
-            env: ProcessingEnvironment,
-            name: String): VariableElement {
-        return getAllFieldsIncludingPrivateSupers(env).first {
-            it.simpleName.toString() == name
-        }
-    }
-
-    private fun runTest(handler: TestInvocation.() -> Unit) {
-        simpleRun(TEST_OBJECT) {
-            it.apply { handler() }
-        }.compilesWithoutError()
-    }
-}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/query/QueryWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/query/QueryWriterTest.kt
deleted file mode 100644
index 6401d96..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/solver/query/QueryWriterTest.kt
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.solver.query
-
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.ext.RoomTypeNames.ROOM_SQL_QUERY
-import android.arch.persistence.room.ext.RoomTypeNames.STRING_UTIL
-import android.arch.persistence.room.processor.QueryMethodProcessor
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.writer.QueryWriter
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourceSubjectFactory
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import testCodeGenScope
-
-@RunWith(JUnit4::class)
-class QueryWriterTest {
-    companion object {
-        const val DAO_PREFIX = """
-                package foo.bar;
-                import android.arch.persistence.room.*;
-                import java.util.*;
-                @Dao
-                abstract class MyClass {
-                """
-        const val DAO_SUFFIX = "}"
-        val QUERY = ROOM_SQL_QUERY.toString()
-    }
-
-    @Test
-    fun simpleNoArgQuery() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users")
-                abstract java.util.List<Integer> selectAllIds();
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`("""
-                    final java.lang.String _sql = "SELECT id FROM users";
-                    final $QUERY _stmt = $QUERY.acquire(_sql, 0);
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun simpleStringArgs() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE name LIKE :name")
-                abstract java.util.List<Integer> selectAllIds(String name);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`(
-                    """
-                    final java.lang.String _sql = "SELECT id FROM users WHERE name LIKE ?";
-                    final $QUERY _stmt = $QUERY.acquire(_sql, 1);
-                    int _argIndex = 1;
-                    if (name == null) {
-                      _stmt.bindNull(_argIndex);
-                    } else {
-                      _stmt.bindString(_argIndex, name);
-                    }
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun twoIntArgs() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE id IN(:id1,:id2)")
-                abstract java.util.List<Integer> selectAllIds(int id1, int id2);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`(
-                    """
-                    final java.lang.String _sql = "SELECT id FROM users WHERE id IN(?,?)";
-                    final $QUERY _stmt = $QUERY.acquire(_sql, 2);
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, id1);
-                    _argIndex = 2;
-                    _stmt.bindLong(_argIndex, id2);
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun aLongAndIntVarArg() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
-                abstract java.util.List<Integer> selectAllIds(long time, int... ids);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`(
-                    """
-                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
-                    _stringBuilder.append("SELECT id FROM users WHERE id IN(");
-                    final int _inputSize = ids.length;
-                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
-                    _stringBuilder.append(") AND age > ");
-                    _stringBuilder.append("?");
-                    final java.lang.String _sql = _stringBuilder.toString();
-                    final int _argCount = 1 + _inputSize;
-                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
-                    int _argIndex = 1;
-                    for (int _item : ids) {
-                      _stmt.bindLong(_argIndex, _item);
-                      _argIndex ++;
-                    }
-                    _argIndex = 1 + _inputSize;
-                    _stmt.bindLong(_argIndex, time);
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    val collectionOut = """
-                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
-                    _stringBuilder.append("SELECT id FROM users WHERE id IN(");
-                    final int _inputSize = ids.size();
-                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
-                    _stringBuilder.append(") AND age > ");
-                    _stringBuilder.append("?");
-                    final java.lang.String _sql = _stringBuilder.toString();
-                    final int _argCount = 1 + _inputSize;
-                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
-                    int _argIndex = 1;
-                    for (java.lang.Integer _item : ids) {
-                      if (_item == null) {
-                        _stmt.bindNull(_argIndex);
-                      } else {
-                        _stmt.bindLong(_argIndex, _item);
-                      }
-                      _argIndex ++;
-                    }
-                    _argIndex = 1 + _inputSize;
-                    _stmt.bindLong(_argIndex, time);
-                    """.trimIndent()
-
-    @Test
-    fun aLongAndIntegerList() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
-                abstract List<Integer> selectAllIds(long time, List<Integer> ids);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`(collectionOut))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun aLongAndIntegerSet() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
-                abstract List<Integer> selectAllIds(long time, Set<Integer> ids);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`(collectionOut))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testMultipleBindParamsWithSameName() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE age > :age OR bage > :age")
-                abstract List<Integer> selectAllIds(int age);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`("""
-                    final java.lang.String _sql = "SELECT id FROM users WHERE age > ? OR bage > ?";
-                    final $QUERY _stmt = $QUERY.acquire(_sql, 2);
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, age);
-                    _argIndex = 2;
-                    _stmt.bindLong(_argIndex, age);
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testMultipleBindParamsWithSameNameWithVarArg() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE age > :age OR bage > :age OR fage IN(:ages)")
-                abstract List<Integer> selectAllIds(int age, int... ages);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`("""
-                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
-                    _stringBuilder.append("SELECT id FROM users WHERE age > ");
-                    _stringBuilder.append("?");
-                    _stringBuilder.append(" OR bage > ");
-                    _stringBuilder.append("?");
-                    _stringBuilder.append(" OR fage IN(");
-                    final int _inputSize = ages.length;
-                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
-                    _stringBuilder.append(")");
-                    final java.lang.String _sql = _stringBuilder.toString();
-                    final int _argCount = 2 + _inputSize;
-                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
-                    int _argIndex = 1;
-                    _stmt.bindLong(_argIndex, age);
-                    _argIndex = 2;
-                    _stmt.bindLong(_argIndex, age);
-                    _argIndex = 3;
-                    for (int _item : ages) {
-                      _stmt.bindLong(_argIndex, _item);
-                      _argIndex ++;
-                    }
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testMultipleBindParamsWithSameNameWithVarArgInTwoBindings() {
-        singleQueryMethod("""
-                @Query("SELECT id FROM users WHERE age IN (:ages) OR bage > :age OR fage IN(:ages)")
-                abstract List<Integer> selectAllIds(int age, int... ages);
-                """) { writer ->
-            val scope = testCodeGenScope()
-            writer.prepareReadAndBind("_sql", "_stmt", scope)
-            assertThat(scope.generate().trim(), `is`("""
-                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
-                    _stringBuilder.append("SELECT id FROM users WHERE age IN (");
-                    final int _inputSize = ages.length;
-                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
-                    _stringBuilder.append(") OR bage > ");
-                    _stringBuilder.append("?");
-                    _stringBuilder.append(" OR fage IN(");
-                    final int _inputSize_1 = ages.length;
-                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize_1);
-                    _stringBuilder.append(")");
-                    final java.lang.String _sql = _stringBuilder.toString();
-                    final int _argCount = 1 + _inputSize + _inputSize_1;
-                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
-                    int _argIndex = 1;
-                    for (int _item : ages) {
-                      _stmt.bindLong(_argIndex, _item);
-                      _argIndex ++;
-                    }
-                    _argIndex = 1 + _inputSize;
-                    _stmt.bindLong(_argIndex, age);
-                    _argIndex = 2 + _inputSize;
-                    for (int _item_1 : ages) {
-                      _stmt.bindLong(_argIndex, _item_1);
-                      _argIndex ++;
-                    }
-                    """.trimIndent()))
-        }.compilesWithoutError()
-    }
-
-    fun singleQueryMethod(vararg input: String,
-                          handler: (QueryWriter) -> Unit):
-            CompileTester {
-        return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
-                .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
-                ))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(Query::class, Dao::class)
-                        .nextRunHandler { invocation ->
-                            val (owner, methods) = invocation.roundEnv
-                                    .getElementsAnnotatedWith(Dao::class.java)
-                                    .map {
-                                        Pair(it,
-                                                invocation.processingEnv.elementUtils
-                                                        .getAllMembers(MoreElements.asType(it))
-                                                        .filter {
-                                                            MoreElements.isAnnotationPresent(it,
-                                                                    Query::class.java)
-                                                        }
-                                        )
-                                    }.filter { it.second.isNotEmpty() }.first()
-                            val parser = QueryMethodProcessor(
-                                    baseContext = invocation.context,
-                                    containing = MoreTypes.asDeclared(owner.asType()),
-                                    executableElement = MoreElements.asExecutable(methods.first()))
-                            val parsedQuery = parser.process()
-                            handler(QueryWriter(parsedQuery))
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/InProcessorTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/InProcessorTest.kt
deleted file mode 100644
index 31e1209..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/InProcessorTest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.testing
-
-import android.arch.persistence.room.Query
-import com.google.common.truth.Truth
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourceSubjectFactory
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import java.util.concurrent.atomic.AtomicBoolean
-
-@RunWith(JUnit4::class)
-class InProcessorTest {
-    @Test
-    fun testInProcessorTestRuns() {
-        val didRun = AtomicBoolean(false)
-        Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
-                .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        """
-                        package foo.bar;
-                        abstract public class MyClass {
-                        @android.arch.persistence.room.Query("foo")
-                        abstract public void setFoo(String foo);
-                        }
-                        """))
-                .processedWith(TestProcessor.builder()
-                        .nextRunHandler { invocation ->
-                            didRun.set(true)
-                            assertThat(invocation.annotations.size, `is`(1))
-                            true
-                        }
-                        .forAnnotations(Query::class)
-                        .build())
-                .compilesWithoutError()
-        assertThat(didRun.get(), `is`(true))
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestInvocation.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestInvocation.kt
deleted file mode 100644
index e03f1db..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestInvocation.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.testing
-
-import android.arch.persistence.room.processor.Context
-import javax.annotation.processing.ProcessingEnvironment
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.TypeElement
-
-data class TestInvocation(val processingEnv: ProcessingEnvironment,
-                          val annotations: MutableSet<out TypeElement>,
-                          val roundEnv: RoundEnvironment) {
-    val context = Context(processingEnv)
-
-    fun typeElement(qName: String): TypeElement {
-        return processingEnv.elementUtils.getTypeElement(qName)
-    }
-
-    val typeUtils by lazy { processingEnv.typeUtils }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestProcessor.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestProcessor.kt
deleted file mode 100644
index 6dfa2e4..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/TestProcessor.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.testing
-
-import javax.annotation.processing.AbstractProcessor
-import javax.annotation.processing.RoundEnvironment
-import javax.annotation.processing.SupportedSourceVersion
-import javax.lang.model.SourceVersion
-import javax.lang.model.element.TypeElement
-import kotlin.reflect.KClass
-
-@SupportedSourceVersion(SourceVersion.RELEASE_8)// test are compiled w/ J_8
-class TestProcessor(val handlers: List<(TestInvocation) -> Boolean>,
-                    val annotations: MutableSet<String>) : AbstractProcessor() {
-    var count = 0
-    override fun process(
-            annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
-        return handlers.getOrNull(count++)?.invoke(
-                    TestInvocation(processingEnv, annotations, roundEnv)) ?: true
-    }
-
-    override fun getSupportedAnnotationTypes(): MutableSet<String> {
-        return annotations
-    }
-
-    class Builder {
-        private var handlers = arrayListOf<(TestInvocation) -> Boolean>()
-        private var annotations = mutableSetOf<String>()
-        fun nextRunHandler(f: (TestInvocation) -> Boolean): Builder {
-            handlers.add(f)
-            return this
-        }
-
-        fun forAnnotations(vararg klasses: KClass<*>): Builder {
-            annotations.addAll(klasses.map { it.java.canonicalName })
-            return this
-        }
-
-        fun build(): TestProcessor {
-            if (annotations.isEmpty()) {
-                throw IllegalStateException("must provide at least 1 annotation")
-            }
-            if (handlers.isEmpty()) {
-                throw IllegalStateException("must provide at least 1 handler")
-            }
-            return TestProcessor(handlers, annotations)
-        }
-    }
-
-    companion object {
-        fun builder(): Builder = Builder()
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt
deleted file mode 100644
index c0167b3..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/testing/test_util.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import android.arch.persistence.room.ColumnInfo
-import android.arch.persistence.room.Embedded
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.Relation
-import android.arch.persistence.room.ext.LifecyclesTypeNames
-import android.arch.persistence.room.ext.PagingTypeNames
-import android.arch.persistence.room.ext.ReactiveStreamsTypeNames
-import android.arch.persistence.room.ext.RoomRxJava2TypeNames
-import android.arch.persistence.room.ext.RxJava2TypeNames
-import android.arch.persistence.room.processor.EntityProcessor
-import android.arch.persistence.room.solver.CodeGenScope
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.verifier.DatabaseVerifier
-import android.arch.persistence.room.writer.ClassWriter
-import com.google.auto.common.MoreElements
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import com.squareup.javapoet.ClassName
-import org.mockito.Mockito
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import java.io.File
-import javax.lang.model.element.Element
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-import javax.tools.JavaFileObject
-
-object COMMON {
-    val USER by lazy {
-        loadJavaCode("common/input/User.java", "foo.bar.User")
-    }
-    val USER_TYPE_NAME by lazy {
-        ClassName.get("foo.bar", "User")
-    }
-    val BOOK by lazy {
-        loadJavaCode("common/input/Book.java", "foo.bar.Book")
-    }
-    val NOT_AN_ENTITY by lazy {
-        loadJavaCode("common/input/NotAnEntity.java", "foo.bar.NotAnEntity")
-    }
-
-    val NOT_AN_ENTITY_TYPE_NAME by lazy {
-        ClassName.get("foo.bar", "NotAnEntity")
-    }
-
-    val MULTI_PKEY_ENTITY by lazy {
-        loadJavaCode("common/input/MultiPKeyEntity.java", "MultiPKeyEntity")
-    }
-    val LIVE_DATA by lazy {
-        loadJavaCode("common/input/LiveData.java", LifecyclesTypeNames.LIVE_DATA.toString())
-    }
-    val COMPUTABLE_LIVE_DATA by lazy {
-        loadJavaCode("common/input/ComputableLiveData.java",
-                LifecyclesTypeNames.COMPUTABLE_LIVE_DATA.toString())
-    }
-    val PUBLISHER by lazy {
-        loadJavaCode("common/input/reactivestreams/Publisher.java",
-                ReactiveStreamsTypeNames.PUBLISHER.toString())
-    }
-    val FLOWABLE by lazy {
-        loadJavaCode("common/input/rxjava2/Flowable.java", RxJava2TypeNames.FLOWABLE.toString())
-    }
-
-    val RX2_ROOM by lazy {
-        loadJavaCode("common/input/Rx2Room.java", RoomRxJava2TypeNames.RX_ROOM.toString())
-    }
-
-    val DATA_SOURCE_FACTORY by lazy {
-        loadJavaCode("common/input/DataSource.java", "android.arch.paging.DataSource")
-    }
-
-    val POSITIONAL_DATA_SOURCE by lazy {
-        loadJavaCode("common/input/PositionalDataSource.java",
-                PagingTypeNames.POSITIONAL_DATA_SOURCE.toString())
-    }
-}
-fun testCodeGenScope(): CodeGenScope {
-    return CodeGenScope(Mockito.mock(ClassWriter::class.java))
-}
-
-fun simpleRun(vararg jfos: JavaFileObject, f: (TestInvocation) -> Unit): CompileTester {
-    return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-            .that(jfos.toList() + JavaFileObjects.forSourceString("foo.bar.MyClass",
-                    """
-                    package foo.bar;
-                    abstract public class MyClass {
-                    @android.arch.persistence.room.Query("foo")
-                    abstract public void setFoo(String foo);
-                    }
-                    """))
-            .processedWith(TestProcessor.builder()
-                    .nextRunHandler {
-                        f(it)
-                        true
-                    }
-                    .forAnnotations(Query::class, PrimaryKey::class, Embedded::class,
-                            ColumnInfo::class, Relation::class, Entity::class)
-                    .build())
-}
-
-fun loadJavaCode(fileName: String, qName: String): JavaFileObject {
-    val contents = File("src/test/data/$fileName").readText(Charsets.UTF_8)
-    return JavaFileObjects.forSourceString(qName, contents)
-}
-
-fun createVerifierFromEntities(invocation: TestInvocation): DatabaseVerifier {
-    val entities = invocation.roundEnv.getElementsAnnotatedWith(Entity::class.java).map {
-        EntityProcessor(invocation.context, MoreElements.asType(it)).process()
-    }
-    return DatabaseVerifier.create(invocation.context, Mockito.mock(Element::class.java),
-            entities)!!
-}
-
-/**
- * Create mocks of [Element] and [TypeMirror] so that they can be used for instantiating a fake
- * [android.arch.persistence.room.vo.Field].
- */
-fun mockElementAndType(): Pair<Element, TypeMirror> {
-    val element = mock(Element::class.java)
-    val type = mock(TypeMirror::class.java)
-    doReturn(TypeKind.DECLARED).`when`(type).kind
-    doReturn(type).`when`(element).asType()
-    return element to type
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/verifier/DatabaseVerifierTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/verifier/DatabaseVerifierTest.kt
deleted file mode 100644
index f368bb2..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/verifier/DatabaseVerifierTest.kt
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.verifier
-
-import android.arch.persistence.room.parser.Collate
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import android.arch.persistence.room.processor.Context
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.vo.CallType
-import android.arch.persistence.room.vo.Constructor
-import android.arch.persistence.room.vo.Database
-import android.arch.persistence.room.vo.Entity
-import android.arch.persistence.room.vo.Field
-import android.arch.persistence.room.vo.FieldGetter
-import android.arch.persistence.room.vo.FieldSetter
-import android.arch.persistence.room.vo.PrimaryKey
-import collect
-import columnNames
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.hasItem
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import simpleRun
-import java.sql.Connection
-import javax.lang.model.element.Element
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.PrimitiveType
-import javax.lang.model.type.TypeKind
-import javax.lang.model.type.TypeMirror
-
-@RunWith(Parameterized::class)
-class DatabaseVerifierTest(private val useLocalizedCollation: Boolean) {
-    @Test
-    fun testSimpleDatabase() {
-        simpleRun { invocation ->
-            val verifier = createVerifier(invocation)
-            val stmt = verifier.connection.createStatement()
-            val rs = stmt.executeQuery("select * from sqlite_master WHERE type='table'")
-            assertThat(
-                    rs.collect { set -> set.getString("name") }, hasItem(`is`("User")))
-            val table = verifier.connection.prepareStatement("select * from User")
-            assertThat(table.columnNames(), `is`(listOf("id", "name", "lastName", "ratio")))
-
-            assertThat(getPrimaryKeys(verifier.connection, "User"), `is`(listOf("id")))
-        }.compilesWithoutError()
-    }
-
-    private fun createVerifier(invocation: TestInvocation): DatabaseVerifier {
-        return DatabaseVerifier.create(invocation.context, mock(Element::class.java),
-                userDb(invocation.context).entities)!!
-    }
-
-    @Test
-    fun testFullEntityQuery() {
-        validQueryTest("select * from User") {
-            assertThat(it, `is`(
-                    QueryResultInfo(listOf(
-                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
-                            ColumnInfo("name", SQLTypeAffinity.TEXT),
-                            ColumnInfo("lastName", SQLTypeAffinity.TEXT),
-                            ColumnInfo("ratio", SQLTypeAffinity.REAL)
-                    ))))
-        }
-    }
-
-    @Test
-    fun testPartialFields() {
-        validQueryTest("select id, lastName from User") {
-            assertThat(it, `is`(
-                    QueryResultInfo(listOf(
-                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
-                            ColumnInfo("lastName", SQLTypeAffinity.TEXT)
-                    ))))
-        }
-    }
-
-    @Test
-    fun testRenamedField() {
-        validQueryTest("select id as myId, lastName from User") {
-            assertThat(it, `is`(
-                    QueryResultInfo(listOf(
-                            ColumnInfo("myId", SQLTypeAffinity.INTEGER),
-                            ColumnInfo("lastName", SQLTypeAffinity.TEXT)
-                    ))))
-        }
-    }
-
-    @Test
-    fun testGrouped() {
-        validQueryTest("select MAX(ratio) from User GROUP BY name") {
-            assertThat(it, `is`(
-                    QueryResultInfo(listOf(
-                            // unfortunately, we don't get this information
-                            ColumnInfo("MAX(ratio)", SQLTypeAffinity.NULL)
-                    ))))
-        }
-    }
-
-    @Test
-    fun testConcat() {
-        validQueryTest("select name || lastName as mergedName from User") {
-            assertThat(it, `is`(
-                    QueryResultInfo(listOf(
-                            // unfortunately, we don't get this information
-                            ColumnInfo("mergedName", SQLTypeAffinity.NULL)
-                    ))))
-        }
-    }
-
-    @Test
-    fun testResultWithArgs() {
-        validQueryTest("select id, name || lastName as mergedName from User where name LIKE ?") {
-            assertThat(it, `is`(
-                    QueryResultInfo(listOf(
-                            // unfortunately, we don't get this information
-                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
-                            ColumnInfo("mergedName", SQLTypeAffinity.NULL)
-                    ))))
-        }
-    }
-
-    @Test
-    fun testDeleteQuery() {
-        validQueryTest("delete from User where name LIKE ?") {
-            assertThat(it, `is`(QueryResultInfo(emptyList())))
-        }
-    }
-
-    @Test
-    fun testUpdateQuery() {
-        validQueryTest("update User set name = ? WHERE id = ?") {
-            assertThat(it, `is`(QueryResultInfo(emptyList())))
-        }
-    }
-
-    @Test
-    fun testBadQuery() {
-        simpleRun { invocation ->
-            val verifier = createVerifier(invocation)
-            val (_, error) = verifier.analyze("select foo from User")
-            assertThat(error, notNullValue())
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun testCollate() {
-        validQueryTest("SELECT id, name FROM user ORDER BY name COLLATE LOCALIZED ASC") {
-            assertThat(it, `is`(
-                    QueryResultInfo(listOf(
-                            // unfortunately, we don't get this information
-                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
-                            ColumnInfo("name", SQLTypeAffinity.TEXT)
-                    ))))
-        }
-    }
-
-    @Test
-    fun testCollateBasQuery() {
-        simpleRun { invocation ->
-            val verifier = createVerifier(invocation)
-            val (_, error) = verifier.analyze(
-                    "SELECT id, name FROM user ORDER BY name COLLATE LOCALIZEDASC")
-            assertThat(error, notNullValue())
-        }.compilesWithoutError()
-    }
-
-    private fun validQueryTest(sql: String, cb: (QueryResultInfo) -> Unit) {
-        simpleRun { invocation ->
-            val verifier = createVerifier(invocation)
-            val info = verifier.analyze(sql)
-            cb(info)
-        }.compilesWithoutError()
-    }
-
-    private fun userDb(context: Context): Database {
-        return database(entity("User",
-                field("id", primitive(context, TypeKind.INT), SQLTypeAffinity.INTEGER),
-                field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
-                field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
-                field("ratio", primitive(context, TypeKind.FLOAT), SQLTypeAffinity.REAL)))
-    }
-
-    private fun database(vararg entities: Entity): Database {
-        return Database(
-                element = mock(TypeElement::class.java),
-                type = mock(TypeMirror::class.java),
-                entities = entities.toList(),
-                daoMethods = emptyList(),
-                version = -1,
-                exportSchema = false,
-                enableForeignKeys = false)
-    }
-
-    private fun entity(tableName: String, vararg fields: Field): Entity {
-        return Entity(
-                element = mock(TypeElement::class.java),
-                tableName = tableName,
-                type = mock(DeclaredType::class.java),
-                fields = fields.toList(),
-                embeddedFields = emptyList(),
-                indices = emptyList(),
-                primaryKey = PrimaryKey(null, fields.take(1), false),
-                foreignKeys = emptyList(),
-                constructor = Constructor(mock(ExecutableElement::class.java), emptyList())
-        )
-    }
-
-    private fun field(name: String, type: TypeMirror, affinity: SQLTypeAffinity): Field {
-        val element = mock(Element::class.java)
-        doReturn(type).`when`(element).asType()
-        val f = Field(
-                element = element,
-                name = name,
-                type = type,
-                columnName = name,
-                affinity = affinity,
-                collate = if (useLocalizedCollation && affinity == SQLTypeAffinity.TEXT) {
-                    Collate.LOCALIZED
-                } else {
-                    null
-                }
-        )
-        assignGetterSetter(f, name, type)
-        return f
-    }
-
-    private fun assignGetterSetter(f: Field, name: String, type: TypeMirror) {
-        f.getter = FieldGetter(name, type, CallType.FIELD)
-        f.setter = FieldSetter(name, type, CallType.FIELD)
-    }
-
-    private fun primitive(context: Context, kind: TypeKind): PrimitiveType {
-        return context.processingEnv.typeUtils.getPrimitiveType(kind)
-    }
-
-    private fun getPrimaryKeys(connection: Connection, tableName: String): List<String> {
-        val stmt = connection.createStatement()
-        val resultSet = stmt.executeQuery("PRAGMA table_info($tableName)")
-        return resultSet.collect {
-            Pair(it.getString("name"), it.getInt("pk"))
-        }
-                .filter { it.second > 0 }
-                .sortedBy { it.second }
-                .map { it.first }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "useLocalizedCollation={0}")
-        @JvmStatic
-        fun params() = arrayListOf(true, false)
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/EntityTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/EntityTest.kt
deleted file mode 100644
index 8afa935..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/EntityTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2018 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.vo
-
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import javax.lang.model.element.Element
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-
-@RunWith(JUnit4::class)
-class EntityTest {
-
-    @Test
-    fun shouldBeDeletedAfter() {
-        val child = createEntity("Child", listOf(
-                createForeignKey("NoAction", ForeignKeyAction.NO_ACTION, false),
-                createForeignKey("NoActionDeferred", ForeignKeyAction.NO_ACTION, true),
-                createForeignKey("Restrict", ForeignKeyAction.RESTRICT, false),
-                createForeignKey("RestrictDeferred", ForeignKeyAction.RESTRICT, true),
-                createForeignKey("SetNull", ForeignKeyAction.SET_NULL, false),
-                createForeignKey("SetNullDeferred", ForeignKeyAction.SET_NULL, true),
-                createForeignKey("SetDefault", ForeignKeyAction.SET_DEFAULT, false),
-                createForeignKey("SetDefaultDeferred", ForeignKeyAction.SET_DEFAULT, true),
-                createForeignKey("Cascade", ForeignKeyAction.CASCADE, false),
-                createForeignKey("CascadeDeferred", ForeignKeyAction.CASCADE, true)))
-        val noAction = createEntity("NoAction")
-        val noActionDeferred = createEntity("NoActionDeferred")
-        val restrict = createEntity("Restrict")
-        val restrictDeferred = createEntity("RestrictDeferred")
-        val setNull = createEntity("SetNull")
-        val setNullDeferred = createEntity("SetNullDeferred")
-        val setDefault = createEntity("SetDefault")
-        val setDefaultDeferred = createEntity("SetDefaultDeferred")
-        val cascade = createEntity("Cascade")
-        val cascadeDeferred = createEntity("CascadeDeferred")
-        val irrelevant = createEntity("Irrelevant")
-        assertThat(child.shouldBeDeletedAfter(noAction), `is`(true))
-        assertThat(child.shouldBeDeletedAfter(noActionDeferred), `is`(false))
-        assertThat(child.shouldBeDeletedAfter(restrict), `is`(true))
-        assertThat(child.shouldBeDeletedAfter(restrictDeferred), `is`(true))
-        assertThat(child.shouldBeDeletedAfter(setNull), `is`(false))
-        assertThat(child.shouldBeDeletedAfter(setNullDeferred), `is`(false))
-        assertThat(child.shouldBeDeletedAfter(setDefault), `is`(false))
-        assertThat(child.shouldBeDeletedAfter(setDefaultDeferred), `is`(false))
-        assertThat(child.shouldBeDeletedAfter(cascade), `is`(false))
-        assertThat(child.shouldBeDeletedAfter(cascadeDeferred), `is`(false))
-        assertThat(child.shouldBeDeletedAfter(irrelevant), `is`(false))
-    }
-
-    private fun createEntity(
-            tableName: String,
-            foreignKeys: List<ForeignKey> = emptyList()): Entity {
-        return Entity(
-                element = mock(TypeElement::class.java),
-                tableName = tableName,
-                type = mock(DeclaredType::class.java),
-                fields = emptyList(),
-                embeddedFields = emptyList(),
-                primaryKey = PrimaryKey(mock(Element::class.java), emptyList(), false),
-                indices = emptyList(),
-                foreignKeys = foreignKeys,
-                constructor = Constructor(mock(ExecutableElement::class.java), emptyList()))
-    }
-
-    private fun createForeignKey(
-            parentTable: String,
-            onDelete: ForeignKeyAction,
-            deferred: Boolean): ForeignKey {
-        return ForeignKey(
-                parentTable = parentTable,
-                parentColumns = emptyList(),
-                childFields = emptyList(),
-                onDelete = onDelete,
-                onUpdate = ForeignKeyAction.NO_ACTION,
-                deferred = deferred)
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/IndexTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/IndexTest.kt
deleted file mode 100644
index 04e43f8..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/vo/IndexTest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.vo
-
-import android.arch.persistence.room.parser.SQLTypeAffinity
-import mockElementAndType
-import org.hamcrest.CoreMatchers
-import org.hamcrest.MatcherAssert
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class IndexTest {
-    @Test
-    fun createSimpleSQL() {
-        val index = Index("foo", false, listOf(mockField("bar"), mockField("baz")))
-        MatcherAssert.assertThat(index.createQuery("my_table"), CoreMatchers.`is`(
-                "CREATE  INDEX `foo` ON `my_table` (`bar`, `baz`)"
-        ))
-    }
-
-    @Test
-    fun createUnique() {
-        val index = Index("foo", true, listOf(mockField("bar"), mockField("baz")))
-        MatcherAssert.assertThat(index.createQuery("my_table"), CoreMatchers.`is`(
-                "CREATE UNIQUE INDEX `foo` ON `my_table` (`bar`, `baz`)"
-        ))
-    }
-
-    private fun mockField(columnName: String): Field {
-        val (element, type) = mockElementAndType()
-        return Field(
-                element = element,
-                name = columnName + "_field",
-                affinity = SQLTypeAffinity.TEXT,
-                type = type,
-                columnName = columnName
-        )
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DaoWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DaoWriterTest.kt
deleted file mode 100644
index 96d1e3a..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DaoWriterTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import COMMON
-import android.arch.persistence.room.ext.RoomTypeNames
-import android.arch.persistence.room.processor.DaoProcessor
-import android.arch.persistence.room.testing.TestProcessor
-import com.google.auto.common.MoreElements
-import com.google.auto.common.MoreTypes
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import createVerifierFromEntities
-import loadJavaCode
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.tools.JavaFileObject
-
-@RunWith(JUnit4::class)
-class DaoWriterTest {
-    @Test
-    fun complexDao() {
-        singleDao(
-                loadJavaCode("databasewriter/input/ComplexDatabase.java",
-                        "foo.bar.ComplexDatabase"),
-                loadJavaCode("daoWriter/input/ComplexDao.java", "foo.bar.ComplexDao")
-        ).compilesWithoutError().and().generatesSources(
-                loadJavaCode("daoWriter/output/ComplexDao.java", "foo.bar.ComplexDao_Impl")
-        )
-    }
-
-    @Test
-    fun writerDao() {
-        singleDao(
-                loadJavaCode("daoWriter/input/WriterDao.java", "foo.bar.WriterDao")
-        ).compilesWithoutError().and().generatesSources(
-                loadJavaCode("daoWriter/output/WriterDao.java", "foo.bar.WriterDao_Impl")
-        )
-    }
-
-    @Test
-    fun deletionDao() {
-        singleDao(
-                loadJavaCode("daoWriter/input/DeletionDao.java", "foo.bar.DeletionDao")
-        ).compilesWithoutError().and().generatesSources(
-                loadJavaCode("daoWriter/output/DeletionDao.java", "foo.bar.DeletionDao_Impl")
-        )
-    }
-
-    @Test
-    fun updateDao() {
-        singleDao(
-                loadJavaCode("daoWriter/input/UpdateDao.java", "foo.bar.UpdateDao")
-        ).compilesWithoutError().and().generatesSources(
-                loadJavaCode("daoWriter/output/UpdateDao.java", "foo.bar.UpdateDao_Impl")
-        )
-    }
-
-    fun singleDao(vararg jfo: JavaFileObject): CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(jfo.toList() + COMMON.USER + COMMON.MULTI_PKEY_ENTITY + COMMON.BOOK +
-                        COMMON.LIVE_DATA + COMMON.COMPUTABLE_LIVE_DATA)
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(android.arch.persistence.room.Dao::class)
-                        .nextRunHandler { invocation ->
-                            val dao = invocation.roundEnv
-                                    .getElementsAnnotatedWith(
-                                            android.arch.persistence.room.Dao::class.java)
-                                    .first()
-                            val db = invocation.roundEnv
-                                    .getElementsAnnotatedWith(
-                                            android.arch.persistence.room.Database::class.java)
-                                    .firstOrNull()
-                            val dbType = MoreTypes.asDeclared(if (db != null) {
-                                db.asType()
-                            } else {
-                                invocation.context.processingEnv.elementUtils
-                                        .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType()
-                            })
-                            val parser = DaoProcessor(
-                                    baseContext = invocation.context,
-                                    element = MoreElements.asType(dao),
-                                    dbType = dbType,
-                                    dbVerifier = createVerifierFromEntities(invocation))
-                            val parsedDao = parser.process()
-                            DaoWriter(parsedDao, invocation.processingEnv)
-                                    .write(invocation.processingEnv)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DatabaseWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DatabaseWriterTest.kt
deleted file mode 100644
index 824d2d0..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/DatabaseWriterTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import COMMON
-import android.arch.persistence.room.RoomProcessor
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import loadJavaCode
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.tools.JavaFileObject
-
-@RunWith(JUnit4::class)
-class DatabaseWriterTest {
-    @Test
-    fun simpleDb() {
-        singleDb(
-                loadJavaCode("databasewriter/input/ComplexDatabase.java",
-                        "foo.bar.ComplexDatabase"),
-                loadJavaCode("daoWriter/input/ComplexDao.java",
-                        "foo.bar.ComplexDao")
-        ).compilesWithoutError().and().generatesSources(
-                loadJavaCode("databasewriter/output/ComplexDatabase.java",
-                        "foo.bar.ComplexDatabase_Impl")
-        )
-    }
-
-    private fun singleDb(vararg jfo: JavaFileObject): CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(jfo.toList() + COMMON.USER + COMMON.LIVE_DATA + COMMON.COMPUTABLE_LIVE_DATA)
-                .processedWith(RoomProcessor())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriterTest.kt
deleted file mode 100644
index 0376ec0..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/EntityCursorConverterWriterTest.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.processor.BaseEntityParserTest
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeSpec
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import javax.lang.model.element.Modifier
-
-@RunWith(JUnit4::class)
-class EntityCursorConverterWriterTest : BaseEntityParserTest() {
-    companion object {
-        val OUT_PREFIX = """
-            package foo.bar;
-            import android.database.Cursor;
-            import java.lang.SuppressWarnings;
-            import javax.annotation.Generated;
-            @Generated("android.arch.persistence.room.RoomProcessor")
-            @SuppressWarnings("unchecked")
-            public class MyContainerClass {
-            """.trimIndent()
-        const val OUT_SUFFIX = "}"
-    }
-
-    @Test
-    fun generateSimple() {
-        generateAndMatch(
-                """
-                @PrimaryKey
-                private int id;
-                String name;
-                String lastName;
-                int age;
-                public int getId() { return id; }
-                public void setId(int id) { this.id = id; }
-                """,
-                """
-                private MyEntity __entityCursorConverter_fooBarMyEntity(Cursor cursor) {
-                  final MyEntity _entity;
-                  final int _cursorIndexOfId = cursor.getColumnIndex("id");
-                  final int _cursorIndexOfName = cursor.getColumnIndex("name");
-                  final int _cursorIndexOfLastName = cursor.getColumnIndex("lastName");
-                  final int _cursorIndexOfAge = cursor.getColumnIndex("age");
-                  _entity = new MyEntity();
-                  if (_cursorIndexOfId != -1) {
-                    final int _tmpId;
-                    _tmpId = cursor.getInt(_cursorIndexOfId);
-                    _entity.setId(_tmpId);
-                  }
-                  if (_cursorIndexOfName != -1) {
-                    _entity.name = cursor.getString(_cursorIndexOfName);
-                  }
-                  if (_cursorIndexOfLastName != -1) {
-                    _entity.lastName = cursor.getString(_cursorIndexOfLastName);
-                  }
-                  if (_cursorIndexOfAge != -1) {
-                    _entity.age = cursor.getInt(_cursorIndexOfAge);
-                  }
-                  return _entity;
-                }
-                """.trimIndent())
-    }
-
-    fun generateAndMatch(input: String, output: String,
-                         attributes: Map<String, String> = mapOf()) {
-        generate(input, attributes)
-                .compilesWithoutError()
-                .and()
-                .generatesSources(JavaFileObjects.forSourceString(
-                        "foo.bar.MyEntity_CursorConverter",
-                        listOf(OUT_PREFIX, output, OUT_SUFFIX).joinToString("\n")))
-    }
-
-    fun generate(input: String, attributes: Map<String, String> = mapOf()): CompileTester {
-        return singleEntity(input, attributes) { entity, invocation ->
-            val className = ClassName.get("foo.bar", "MyContainerClass")
-            val writer = object : ClassWriter(className) {
-                override fun createTypeSpecBuilder(): TypeSpec.Builder {
-                    getOrCreateMethod(EntityCursorConverterWriter(entity))
-                    return TypeSpec.classBuilder(className).apply {
-                        addModifiers(Modifier.PUBLIC)
-                    }
-                }
-            }
-            writer.write(invocation.processingEnv)
-        }
-    }
-}
diff --git a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriterTest.kt b/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriterTest.kt
deleted file mode 100644
index 4d68231..0000000
--- a/room/compiler/src/test/kotlin/android/arch/persistence/room/writer/SQLiteOpenHelperWriterTest.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.writer
-
-import android.arch.persistence.room.processor.DatabaseProcessor
-import android.arch.persistence.room.testing.TestInvocation
-import android.arch.persistence.room.testing.TestProcessor
-import android.arch.persistence.room.vo.Database
-import android.support.annotation.NonNull
-import com.google.auto.common.MoreElements
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class SQLiteOpenHelperWriterTest {
-    companion object {
-        const val ENTITY_PREFIX = """
-            package foo.bar;
-            import android.support.annotation.NonNull;
-            import android.arch.persistence.room.*;
-            @Entity%s
-            public class MyEntity {
-            """
-        const val ENTITY_SUFFIX = "}"
-        const val DATABASE_CODE = """
-            package foo.bar;
-            import android.arch.persistence.room.*;
-            @Database(entities = {MyEntity.class}, version = 3)
-            abstract public class MyDatabase extends RoomDatabase {
-            }
-            """
-    }
-
-    @Test
-    fun createSimpleEntity() {
-        singleEntity(
-                """
-                @PrimaryKey
-                @NonNull
-                String uuid;
-                String name;
-                int age;
-                """.trimIndent()
-        ) { database, _ ->
-            val query = SQLiteOpenHelperWriter(database)
-                    .createQuery(database.entities.first())
-            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
-                    " `MyEntity` (`uuid` TEXT NOT NULL, `name` TEXT, `age` INTEGER NOT NULL," +
-                    " PRIMARY KEY(`uuid`))"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun multiplePrimaryKeys() {
-        singleEntity(
-                """
-                @NonNull
-                String uuid;
-                @NonNull
-                String name;
-                int age;
-                """.trimIndent(), attributes = mapOf("primaryKeys" to "{\"uuid\", \"name\"}")
-        ) { database, _ ->
-            val query = SQLiteOpenHelperWriter(database)
-                    .createQuery(database.entities.first())
-            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
-                    " `MyEntity` (`uuid` TEXT NOT NULL, `name` TEXT NOT NULL, " +
-                    "`age` INTEGER NOT NULL, PRIMARY KEY(`uuid`, `name`))"))
-        }.compilesWithoutError()
-    }
-
-    @Test
-    fun autoIncrementObject() {
-        listOf("Long", "Integer").forEach { type ->
-            singleEntity(
-                    """
-                @PrimaryKey(autoGenerate = true)
-                $type uuid;
-                String name;
-                int age;
-                """.trimIndent()
-            ) { database, _ ->
-                val query = SQLiteOpenHelperWriter(database)
-                        .createQuery(database.entities.first())
-                assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
-                        " `MyEntity` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT," +
-                        " `name` TEXT, `age` INTEGER NOT NULL)"))
-            }.compilesWithoutError()
-        }
-    }
-
-    @Test
-    fun autoIncrementPrimitives() {
-        listOf("long", "int").forEach { type ->
-            singleEntity(
-                    """
-                @PrimaryKey(autoGenerate = true)
-                $type uuid;
-                String name;
-                int age;
-                """.trimIndent()
-            ) { database, _ ->
-                val query = SQLiteOpenHelperWriter(database)
-                        .createQuery(database.entities.first())
-                assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
-                        " `MyEntity` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
-                        " `name` TEXT, `age` INTEGER NOT NULL)"))
-            }.compilesWithoutError()
-        }
-    }
-
-    fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
-                     handler: (Database, TestInvocation) -> Unit): CompileTester {
-        val attributesReplacement: String
-        if (attributes.isEmpty()) {
-            attributesReplacement = ""
-        } else {
-            attributesReplacement = "(" +
-                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
-                    ")".trimIndent()
-        }
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
-                        ENTITY_PREFIX.format(attributesReplacement) + input + ENTITY_SUFFIX
-                ), JavaFileObjects.forSourceString("foo.bar.MyDatabase",
-                        DATABASE_CODE)))
-                .processedWith(TestProcessor.builder()
-                        .forAnnotations(android.arch.persistence.room.Database::class,
-                                NonNull::class)
-                        .nextRunHandler { invocation ->
-                            val db = MoreElements.asType(invocation.roundEnv
-                                    .getElementsAnnotatedWith(
-                                            android.arch.persistence.room.Database::class.java)
-                                    .first())
-                            handler(DatabaseProcessor(invocation.context, db).process(), invocation)
-                            true
-                        }
-                        .build())
-    }
-}
diff --git a/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt b/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
new file mode 100644
index 0000000..0dff752
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.parser
+
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SqlParserTest {
+
+    @Test
+    fun multipleQueries() {
+        assertErrors("SELECT * FROM users; SELECT * FROM books;",
+                ParserErrors.NOT_ONE_QUERY)
+    }
+
+    @Test
+    fun empty() {
+        assertErrors("", ParserErrors.NOT_ONE_QUERY)
+    }
+
+    @Test
+    fun deleteQuery() {
+        val parsed = SqlParser.parse("DELETE FROM users where id > 3")
+        assertThat(parsed.errors, `is`(emptyList()))
+        assertThat(parsed.type, `is`(QueryType.DELETE))
+    }
+
+    @Test
+    fun badDeleteQuery() {
+        assertErrors("delete from user where mAge >= :min && mAge <= :max",
+                "no viable alternative at input 'delete from user where mAge >= :min &&'")
+    }
+
+    @Test
+    fun updateQuery() {
+        val parsed = SqlParser.parse("UPDATE users set name = :name where id = :id")
+        assertThat(parsed.errors, `is`(emptyList()))
+        assertThat(parsed.type, `is`(QueryType.UPDATE))
+    }
+
+    @Test
+    fun explain() {
+        assertErrors("EXPLAIN QUERY PLAN SELECT * FROM users",
+                ParserErrors.invalidQueryType(QueryType.EXPLAIN))
+    }
+
+    @Test
+    fun validColumnNames() {
+        listOf("f", "fo", "f2", "f 2", "foo_2", "foo-2", "_", "foo bar baz",
+                "foo 2 baz", "_baz", "fooBar", "2", "*", "foo*2", "dsa$", "\$fsa",
+                "-bar", "ÅŸoöÄŸüı").forEach {
+            assertThat("name: $it", SqlParser.isValidIdentifier(it), `is`(true))
+        }
+    }
+
+    @Test
+    fun invalidColumnNames() {
+        listOf("", " ", "fd`a`", "f`a", "`a", "\"foo bar\"", "\"", "`").forEach {
+            assertThat("name: $it", SqlParser.isValidIdentifier(it), `is`(false))
+        }
+    }
+
+    @Test
+    fun extractTableNames() {
+        assertThat(SqlParser.parse("select * from users").tables,
+                `is`(setOf(Table("users", "users"))))
+        assertThat(SqlParser.parse("select * from users as ux").tables,
+                `is`(setOf(Table("users", "ux"))))
+        assertThat(SqlParser.parse("select * from (select * from books)").tables,
+                `is`(setOf(Table("books", "books"))))
+        assertThat(SqlParser.parse("select x.id from (select * from books) as x").tables,
+                `is`(setOf(Table("books", "books"))))
+    }
+
+    @Test
+    fun unescapeTableNames() {
+        assertThat(SqlParser.parse("select * from `users`").tables,
+                `is`(setOf(Table("users", "users"))))
+        assertThat(SqlParser.parse("select * from \"users\"").tables,
+                `is`(setOf(Table("users", "users"))))
+        assertThat(SqlParser.parse("select * from 'users'").tables,
+                `is`(setOf(Table("users", "users"))))
+    }
+
+    @Test
+    fun tablePrefixInInsert_set() {
+        // this is an invalid query, b/64539805
+        val query = SqlParser.parse("UPDATE trips SET trips.title=:title")
+        assertThat(query.errors, not(emptyList()))
+    }
+
+    @Test
+    fun tablePrefixInInsert_where() {
+        val query = SqlParser.parse("UPDATE trips SET title=:title WHERE trips.id=:id")
+        assertThat(query.errors, `is`(emptyList()))
+    }
+
+    @Test
+    fun tablePrefixInSelect_projection() {
+        val query = SqlParser.parse("SELECT a.name, b.last_name from user a, book b")
+        assertThat(query.errors, `is`(emptyList()))
+        assertThat(query.tables, `is`(setOf(Table("user", "a"),
+                Table("book", "b"))))
+    }
+
+    @Test
+    fun tablePrefixInSelect_where() {
+        val query = SqlParser.parse("SELECT a.name, b.last_name from user a, book b" +
+                " WHERE a.name = b.name")
+        assertThat(query.errors, `is`(emptyList()))
+        assertThat(query.tables, `is`(setOf(Table("user", "a"),
+                Table("book", "b"))))
+    }
+
+    @Test
+    fun findBindVariables() {
+        assertVariables("select * from users")
+        assertVariables("select * from users where name like ?", "?")
+        assertVariables("select * from users where name like :name", ":name")
+        assertVariables("select * from users where name like ?2", "?2")
+        assertVariables("select * from users where name like ?2 OR name LIKE ?1", "?2", "?1")
+        assertVariables("select * from users where name like @a", "@a")
+        assertVariables("select * from users where name like \$a", "\$a")
+    }
+
+    @Test
+    fun indexedVariablesError() {
+        assertErrors("select * from users where name like ?",
+                ParserErrors.ANONYMOUS_BIND_ARGUMENT)
+        assertErrors("select * from users where name like ? or last_name like ?",
+                ParserErrors.ANONYMOUS_BIND_ARGUMENT)
+        assertErrors("select * from users where name like ?1",
+                ParserErrors.cannotUseVariableIndices("?1", 36))
+    }
+
+    @Test
+    fun foo() {
+        assertSections("select * from users where name like ?",
+                Section.text("select * from users where name like "),
+                Section.bindVar("?"))
+
+        assertSections("select * from users where name like :name AND last_name like :lastName",
+                Section.text("select * from users where name like "),
+                Section.bindVar(":name"),
+                Section.text(" AND last_name like "),
+                Section.bindVar(":lastName"))
+
+        assertSections("select * from users where name \nlike :name AND last_name like :lastName",
+                Section.text("select * from users where name "),
+                Section.newline(),
+                Section.text("like "),
+                Section.bindVar(":name"),
+                Section.text(" AND last_name like "),
+                Section.bindVar(":lastName"))
+
+        assertSections("select * from users where name like :name \nAND last_name like :lastName",
+                Section.text("select * from users where name like "),
+                Section.bindVar(":name"),
+                Section.text(" "),
+                Section.newline(),
+                Section.text("AND last_name like "),
+                Section.bindVar(":lastName"))
+
+        assertSections("select * from users where name like :name \nAND last_name like \n:lastName",
+                Section.text("select * from users where name like "),
+                Section.bindVar(":name"),
+                Section.text(" "),
+                Section.newline(),
+                Section.text("AND last_name like "),
+                Section.newline(),
+                Section.bindVar(":lastName"))
+    }
+
+    fun assertVariables(query: String, vararg expected: String) {
+        assertThat((SqlParser.parse(query)).inputs.map { it.text }, `is`(expected.toList()))
+    }
+
+    fun assertErrors(query: String, vararg errors: String) {
+        assertThat((SqlParser.parse(query)).errors, `is`(errors.toList()))
+    }
+
+    fun assertSections(query: String, vararg sections: Section) {
+        assertThat(SqlParser.parse(query).sections, `is`(sections.toList()))
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
new file mode 100644
index 0000000..b3d42b7
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/BaseDaoTest.kt
@@ -0,0 +1,169 @@
+package androidx.room.processor
+
+import COMMON
+import androidx.room.ext.RoomTypeNames
+import androidx.room.vo.Dao
+import androidx.room.writer.DaoWriter
+import com.google.auto.common.MoreTypes
+import com.google.testing.compile.JavaFileObjects
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import simpleRun
+
+/**
+ * we don't assert much in these tests since if type resolution fails, compilation fails.
+ */
+@RunWith(JUnit4::class)
+class BaseDaoTest {
+    private fun String.toJFO(qName: String) = JavaFileObjects.forSourceLines(qName, this)
+
+    @Test
+    fun insert() {
+        baseDao("""
+            @Insert
+            void insertMe(T t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun insertArray() {
+        baseDao("""
+            @Insert
+            void insertMe(T[] t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun insertVarArg() {
+        baseDao("""
+            @Insert
+            void insertMe(T... t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun insertList() {
+        baseDao("""
+            @Insert
+            void insertMe(List<T> t);
+        """) { dao ->
+            assertThat(dao.insertionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun delete() {
+        baseDao("""
+            @Delete
+            void deleteMe(T t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun deleteArray() {
+        baseDao("""
+            @Delete
+            void deleteMe(T[] t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun deleteVarArg() {
+        baseDao("""
+            @Delete
+            void deleteMe(T... t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun deleteList() {
+        baseDao("""
+            @Delete
+            void deleteMe(List<T> t);
+        """) { dao ->
+            assertThat(dao.deletionMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun update() {
+        baseDao("""
+            @Update
+            void updateMe(T t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun updateArray() {
+        baseDao("""
+            @Update
+            void updateMe(T[] t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun updateVarArg() {
+        baseDao("""
+            @Update
+            void updateMe(T... t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    @Test
+    fun updateList() {
+        baseDao("""
+            @Update
+            void updateMe(List<T> t);
+        """) { dao ->
+            assertThat(dao.updateMethods.size, `is`(1))
+        }
+    }
+
+    fun baseDao(code: String, handler: (Dao) -> Unit) {
+        val baseClass = """
+            package foo.bar;
+            import androidx.room.*;
+            import java.util.List;
+
+            interface BaseDao<K, T> {
+                $code
+            }
+        """.toJFO("foo.bar.BaseDao")
+        val extension = """
+            package foo.bar;
+            import androidx.room.*;
+            @Dao
+            interface MyDao extends BaseDao<Integer, User> {
+            }
+        """.toJFO("foo.bar.MyDao")
+        simpleRun(baseClass, extension, COMMON.USER) { invocation ->
+            val daoElm = invocation.processingEnv.elementUtils.getTypeElement("foo.bar.MyDao")
+            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
+                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
+            val processedDao = DaoProcessor(invocation.context, daoElm, dbType, null).process()
+            handler(processedDao)
+            DaoWriter(processedDao, invocation.processingEnv).write(invocation.processingEnv)
+        }.compilesWithoutError()
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
new file mode 100644
index 0000000..0bd4b2f
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/BaseEntityParserTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.annotation.NonNull
+import androidx.room.Embedded
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.Entity
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import javax.tools.JavaFileObject
+
+abstract class BaseEntityParserTest {
+    companion object {
+        const val ENTITY_PREFIX = """
+            package foo.bar;
+            import androidx.room.*;
+            import androidx.annotation.NonNull;
+            @Entity%s
+            public class MyEntity %s {
+            """
+        const val ENTITY_SUFFIX = "}"
+    }
+
+    fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
+                     baseClass: String = "",
+                     jfos: List<JavaFileObject> = emptyList(),
+                     handler: (Entity, TestInvocation) -> Unit): CompileTester {
+        val attributesReplacement: String
+        if (attributes.isEmpty()) {
+            attributesReplacement = ""
+        } else {
+            attributesReplacement = "(" +
+                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
+                    ")".trimIndent()
+        }
+        val baseClassReplacement: String
+        if (baseClass == "") {
+            baseClassReplacement = ""
+        } else {
+            baseClassReplacement = " extends $baseClass"
+        }
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfos + JavaFileObjects.forSourceString("foo.bar.MyEntity",
+                        ENTITY_PREFIX.format(attributesReplacement, baseClassReplacement)
+                                + input + ENTITY_SUFFIX
+                ))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(androidx.room.Entity::class,
+                                androidx.room.PrimaryKey::class,
+                                androidx.room.Ignore::class,
+                                Embedded::class,
+                                androidx.room.ColumnInfo::class,
+                                NonNull::class)
+                        .nextRunHandler { invocation ->
+                            val entity = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            androidx.room.Entity::class.java)
+                                    .first { it.toString() == "foo.bar.MyEntity" }
+                            val parser = EntityProcessor(invocation.context,
+                                    MoreElements.asType(entity))
+                            val parsedQuery = parser.process()
+                            handler(parsedQuery, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
new file mode 100644
index 0000000..40d56e1
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
@@ -0,0 +1,271 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.TypeConverter
+import androidx.room.ext.typeName
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
+import androidx.room.processor.ProcessorErrors
+        .TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
+import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
+import androidx.room.testing.TestInvocation
+import androidx.room.vo.CustomTypeConverter
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import com.squareup.javapoet.TypeVariableName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import simpleRun
+import java.util.Date
+import javax.lang.model.element.Modifier
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class CustomConverterProcessorTest {
+    companion object {
+        val CONVERTER = ClassName.get("foo.bar", "MyConverter")!!
+        val CONVERTER_QNAME = CONVERTER.packageName() + "." + CONVERTER.simpleName()
+        val CONTAINER = JavaFileObjects.forSourceString("foo.bar.Container",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @TypeConverters(foo.bar.MyConverter.class)
+                public class Container {}
+                """)
+    }
+
+    @Test
+    fun validCase() {
+        singleClass(createConverter(TypeName.SHORT.box(), TypeName.CHAR.box())) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveFrom() {
+        singleClass(createConverter(TypeName.SHORT, TypeName.CHAR.box())) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveTo() {
+        singleClass(createConverter(TypeName.INT.box(), TypeName.DOUBLE)) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.INT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveBoth() {
+        singleClass(createConverter(TypeName.INT, TypeName.DOUBLE)) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.INT))
+            assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun nonNullButNotBoxed() {
+        val string = String::class.typeName()
+        val date = Date::class.typeName()
+        singleClass(createConverter(string, date)) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(string as TypeName))
+            assertThat(converter?.toTypeName, `is`(date as TypeName))
+        }
+    }
+
+    @Test
+    fun parametrizedTypeUnbound() {
+        val typeVarT = TypeVariableName.get("T")
+        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
+        val typeVarK = TypeVariableName.get("K")
+        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
+        singleClass(createConverter(list, map, listOf(typeVarK, typeVarT))) {
+            _, _ ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_UNBOUND_GENERIC)
+    }
+
+    @Test
+    fun parametrizedTypeSpecific() {
+        val string = String::class.typeName()
+        val date = Date::class.typeName()
+        val list = ParameterizedTypeName.get(List::class.typeName(), string)
+        val map = ParameterizedTypeName.get(Map::class.typeName(), string, date)
+        singleClass(createConverter(list, map)) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(list as TypeName))
+            assertThat(converter?.toTypeName, `is`(map as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testNoConverters() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                public class ${CONVERTER.simpleName()} {
+                }
+                """)) { _, _ ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
+    }
+
+    @Test
+    fun checkNoArgConstructor() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import androidx.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    public ${CONVERTER.simpleName()}(int x) {}
+                    @TypeConverter
+                    public int x(short y) {return 0;}
+                }
+                """)) { _, _ ->
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+    }
+
+    @Test
+    fun checkNoArgConstructor_withStatic() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import androidx.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    public ${CONVERTER.simpleName()}(int x) {}
+                    @TypeConverter
+                    public static int x(short y) {return 0;}
+                }
+                """)) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.INT))
+            assertThat(converter?.isStatic, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun checkPublic() {
+        singleClass(JavaFileObjects.forSourceString(CONVERTER_QNAME,
+                """
+                package ${CONVERTER.packageName()};
+                import androidx.room.TypeConverter;
+
+                public class ${CONVERTER.simpleName()} {
+                    @TypeConverter static int x(short y) {return 0;}
+                    @TypeConverter private static int y(boolean y) {return 0;}
+                }
+                """)) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
+            assertThat(converter?.toTypeName, `is`(TypeName.INT))
+            assertThat(converter?.isStatic, `is`(true))
+        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MUST_BE_PUBLIC).and()
+                .withErrorCount(2)
+    }
+
+    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+    @Test
+    fun parametrizedTypeBoundViaParent() {
+        val typeVarT = TypeVariableName.get("T")
+        val list = ParameterizedTypeName.get(List::class.typeName(), typeVarT)
+        val typeVarK = TypeVariableName.get("K")
+        val map = ParameterizedTypeName.get(Map::class.typeName(), typeVarK, typeVarT)
+
+        val baseConverter = createConverter(list, map, listOf(typeVarT, typeVarK))
+        val extendingQName = "foo.bar.Extending"
+        val extendingClass = JavaFileObjects.forSourceString(extendingQName,
+                "package foo.bar;\n" +
+                        TypeSpec.classBuilder(ClassName.bestGuess(extendingQName)).apply {
+                            superclass(
+                                    ParameterizedTypeName.get(CONVERTER, String::class.typeName(),
+                                    Integer::class.typeName()))
+                        }.build().toString())
+
+        simpleRun(baseConverter, extendingClass) { invocation ->
+            val element = invocation.processingEnv.elementUtils.getTypeElement(extendingQName)
+            val converter = CustomConverterProcessor(invocation.context, element)
+                    .process().firstOrNull()
+            assertThat(converter?.fromTypeName, `is`(ParameterizedTypeName.get(
+                    List::class.typeName(), String::class.typeName()) as TypeName
+            ))
+            assertThat(converter?.toTypeName, `is`(ParameterizedTypeName.get(Map::class.typeName(),
+                    Integer::class.typeName(), String::class.typeName()) as TypeName
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun checkDuplicates() {
+        singleClass(
+                createConverter(TypeName.SHORT.box(), TypeName.CHAR.box(), duplicate = true)
+        ) { converter, _ ->
+            assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
+            assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
+        }.failsToCompile().withErrorContaining("Multiple methods define the same conversion")
+    }
+
+    private fun createConverter(
+            from: TypeName,
+            to: TypeName,
+            typeVariables: List<TypeVariableName> = emptyList(),
+            duplicate: Boolean = false
+    ): JavaFileObject {
+        val code = TypeSpec.classBuilder(CONVERTER).apply {
+            addTypeVariables(typeVariables)
+            addModifiers(Modifier.PUBLIC)
+            fun buildMethod(name: String) = MethodSpec.methodBuilder(name).apply {
+                addAnnotation(TypeConverter::class.java)
+                addModifiers(Modifier.PUBLIC)
+                returns(to)
+                addParameter(ParameterSpec.builder(from, "input").build())
+                if (to.isPrimitive) {
+                    addStatement("return 0")
+                } else {
+                    addStatement("return null")
+                }
+            }.build()
+            addMethod(buildMethod("convertF"))
+            if (duplicate) {
+                addMethod(buildMethod("convertF2"))
+            }
+        }.build().toString()
+        return JavaFileObjects.forSourceString(CONVERTER.toString(),
+                "package ${CONVERTER.packageName()};\n$code")
+    }
+
+    private fun singleClass(
+            vararg jfo: JavaFileObject,
+            handler: (CustomTypeConverter?, TestInvocation) -> Unit
+    ): CompileTester {
+        return simpleRun(*((jfo.toList() + CONTAINER).toTypedArray())) { invocation ->
+            val processed = CustomConverterProcessor.findConverters(invocation.context,
+                    invocation.processingEnv.elementUtils.getTypeElement("foo.bar.Container"))
+            handler(processed.converters.firstOrNull()?.custom, invocation)
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
new file mode 100644
index 0000000..4842451
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import COMMON
+import androidx.room.ext.RoomTypeNames
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.Dao
+import androidx.room.vo.Warning
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import createVerifierFromEntities
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DaoProcessorTest(val enableVerification: Boolean) {
+    companion object {
+        const val DAO_PREFIX = """
+            package foo.bar;
+            import androidx.room.*;
+            """
+        @Parameterized.Parameters(name = "enableDbVerification={0}")
+        @JvmStatic
+        fun getParams() = arrayOf(true, false)
+    }
+
+    @Test
+    fun testNonAbstract() {
+        singleDao("@Dao public class MyDao {}") { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
+    }
+
+    @Test
+    fun testAbstractMethodWithoutQuery() {
+        singleDao("""
+                @Dao public interface MyDao {
+                    int getFoo();
+                }
+        """) { _, _ ->
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION)
+    }
+
+    @Test
+    fun testBothAnnotations() {
+        singleDao("""
+                @Dao public interface MyDao {
+                    @Query("select 1")
+                    @Insert
+                    int getFoo(int x);
+                }
+        """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD)
+    }
+
+    @Test
+    fun testAbstractClass() {
+        singleDao("""
+                @Dao abstract class MyDao {
+                    @Query("SELECT uid FROM User")
+                    abstract int[] getIds();
+                }
+                """) { dao, _ ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testInterface() {
+        singleDao("""
+                @Dao interface MyDao {
+                    @Query("SELECT uid FROM User")
+                    abstract int[] getIds();
+                }
+                """) { dao, _ ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testWithInsertAndQuery() {
+        singleDao("""
+                @Dao abstract class MyDao {
+                    @Query("SELECT uid FROM User")
+                    abstract int[] getIds();
+                    @Insert
+                    abstract void insert(User user);
+                }
+                """) { dao, _ ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+            assertThat(dao.insertionMethods.size, `is`(1))
+            val insertMethod = dao.insertionMethods.first()
+            assertThat(insertMethod.name, `is`("insert"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun skipQueryVerification() {
+        singleDao("""
+                @Dao @SkipQueryVerification interface MyDao {
+                    @Query("SELECT nonExistingField FROM User")
+                    abstract int[] getIds();
+                }
+                """) { dao, _ ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            val method = dao.queryMethods.first()
+            assertThat(method.name, `is`("getIds"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun suppressedWarnings() {
+        singleDao("""
+            @SuppressWarnings({"ALL", RoomWarnings.CURSOR_MISMATCH})
+            @Dao interface MyDao {
+                @Query("SELECT * from user")
+                abstract User users();
+            }
+            """) { dao, invocation ->
+            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
+                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
+            val daoProcessor = DaoProcessor(invocation.context, dao.element, dbType, null)
+            assertThat(daoProcessor.context.logger
+                    .suppressedWarnings, `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
+
+            dao.queryMethods.forEach {
+                assertThat(QueryMethodProcessor(
+                        baseContext = daoProcessor.context,
+                        containing = MoreTypes.asDeclared(dao.element.asType()),
+                        executableElement = it.element,
+                        dbVerifier = null).context.logger.suppressedWarnings,
+                        `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
+            }
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun suppressedWarningsInheritance() {
+        singleDao("""
+            @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
+            @Dao interface MyDao {
+                @SuppressWarnings("ALL")
+                @Query("SELECT * from user")
+                abstract User users();
+            }
+            """) { dao, invocation ->
+            val dbType = MoreTypes.asDeclared(invocation.context.processingEnv.elementUtils
+                    .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType())
+            val daoProcessor = DaoProcessor(invocation.context, dao.element, dbType, null)
+            assertThat(daoProcessor.context.logger
+                    .suppressedWarnings, `is`(setOf(Warning.CURSOR_MISMATCH)))
+
+            dao.queryMethods.forEach {
+                assertThat(QueryMethodProcessor(
+                        baseContext = daoProcessor.context,
+                        containing = MoreTypes.asDeclared(dao.element.asType()),
+                        executableElement = it.element,
+                        dbVerifier = null).context.logger.suppressedWarnings,
+                        `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH)))
+            }
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun query_warnIfTransactionIsMissingForRelation() {
+        if (!enableVerification) {
+            return
+        }
+        singleDao(
+                """
+                @Dao interface MyDao {
+                    static class Merged extends User {
+                       @Relation(parentColumn = "name", entityColumn = "lastName",
+                                 entity = User.class)
+                       java.util.List<User> users;
+                    }
+                    @Query("select * from user")
+                    abstract java.util.List<Merged> loadUsers();
+                }
+                """
+        ) { dao, _ ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            assertThat(dao.queryMethods.first().inTransaction, `is`(false))
+        }.compilesWithoutError()
+                .withWarningContaining(ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
+    }
+
+    @Test
+    fun query_dontWarnIfTransactionIsMissingForRelation_suppressed() {
+        if (!enableVerification) {
+            return
+        }
+        singleDao(
+                """
+                @Dao interface MyDao {
+                    static class Merged extends User {
+                       @Relation(parentColumn = "name", entityColumn = "lastName",
+                                 entity = User.class)
+                       java.util.List<User> users;
+                    }
+                    @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
+                    @Query("select * from user")
+                    abstract java.util.List<Merged> loadUsers();
+                }
+                """
+        ) { dao, _ ->
+            assertThat(dao.queryMethods.size, `is`(1))
+            assertThat(dao.queryMethods.first().inTransaction, `is`(false))
+        }.compilesWithoutError()
+                .withWarningCount(0)
+    }
+
+    @Test
+    fun query_dontWarnIfTransactionNotIsMissingForRelation() {
+        if (!enableVerification) {
+            return
+        }
+        singleDao(
+                """
+                @Dao interface MyDao {
+                    static class Merged extends User {
+                       @Relation(parentColumn = "name", entityColumn = "lastName",
+                                 entity = User.class)
+                       java.util.List<User> users;
+                    }
+                    @Transaction
+                    @Query("select * from user")
+                    abstract java.util.List<Merged> loadUsers();
+                }
+                """
+        ) { dao, _ ->
+            // test sanity
+            assertThat(dao.queryMethods.size, `is`(1))
+            assertThat(dao.queryMethods.first().inTransaction, `is`(true))
+        }.compilesWithoutError()
+                .withWarningCount(0)
+    }
+
+    fun singleDao(vararg inputs: String, handler: (Dao, TestInvocation) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyDao",
+                        DAO_PREFIX + inputs.joinToString("\n")
+                ), COMMON.USER))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(androidx.room.Dao::class,
+                                androidx.room.Entity::class,
+                                androidx.room.Relation::class,
+                                androidx.room.Transaction::class,
+                                androidx.room.ColumnInfo::class,
+                                androidx.room.PrimaryKey::class,
+                                androidx.room.Query::class)
+                        .nextRunHandler { invocation ->
+                            val dao = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            androidx.room.Dao::class.java)
+                                    .first()
+                            val dbVerifier = if (enableVerification) {
+                                createVerifierFromEntities(invocation)
+                            } else {
+                                null
+                            }
+                            val dbType = MoreTypes.asDeclared(
+                                    invocation.context.processingEnv.elementUtils
+                                            .getTypeElement(RoomTypeNames.ROOM_DB.toString())
+                                            .asType())
+                            val parser = DaoProcessor(invocation.context,
+                                    MoreElements.asType(dao), dbType, dbVerifier)
+
+                            val parsedDao = parser.process()
+                            handler(parsedDao, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
new file mode 100644
index 0000000..8f2231b
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -0,0 +1,712 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import COMMON
+import androidx.room.RoomProcessor
+import androidx.room.solver.query.result.EntityRowAdapter
+import androidx.room.solver.query.result.PojoRowAdapter
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.Database
+import androidx.room.vo.Warning
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ClassName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.sameInstance
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.tools.JavaFileObject
+import javax.tools.StandardLocation
+
+@RunWith(JUnit4::class)
+class DatabaseProcessorTest {
+    companion object {
+        const val DATABASE_PREFIX = """
+            package foo.bar;
+            import androidx.room.*;
+            """
+        val DB1: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db1",
+                """
+                $DATABASE_PREFIX
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db1 extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """)
+        val DB2: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db2",
+                """
+                $DATABASE_PREFIX
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db2 extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """)
+        val DB3: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db3",
+                """
+                $DATABASE_PREFIX
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db3 extends RoomDatabase {
+                }
+                """)
+        val USER: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.User",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity
+                public class User {
+                    @PrimaryKey
+                    int uid;
+                }
+                """)
+        val USER_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.UserDao",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Dao
+                public interface UserDao {
+                    @Query("SELECT * FROM user")
+                    public java.util.List<User> loadAll();
+
+                    @Insert
+                    public void insert(User... users);
+
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public User loadOne(int uid);
+
+                    @TypeConverters(Converter.class)
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public User loadWithConverter(int uid);
+
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public Pojo loadOnePojo(int uid);
+
+                    @Query("SELECT * FROM user")
+                    public java.util.List<Pojo> loadAllPojos();
+
+                    @TypeConverters(Converter.class)
+                    @Query("SELECT * FROM user where uid = :uid")
+                    public Pojo loadPojoWithConverter(int uid);
+
+                    public static class Converter {
+                        @TypeConverter
+                        public static java.util.Date foo(Long input) {return null;}
+                    }
+
+                    public static class Pojo {
+                        public int uid;
+                    }
+                }
+                """)
+        val BOOK: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Book",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity
+                public class Book {
+                    @PrimaryKey
+                    int bookId;
+                }
+                """)
+        val BOOK_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Dao
+                public interface BookDao {
+                    @Query("SELECT * FROM book")
+                    public java.util.List<Book> loadAllBooks();
+                    @Insert
+                    public void insert(Book book);
+                }
+                """)
+    }
+
+    @Test
+    fun simple() {
+        singleDb("""
+            @Database(entities = {User.class}, version = 42)
+            public abstract class MyDb extends RoomDatabase {
+                abstract UserDao userDao();
+            }
+            """, USER, USER_DAO) { db, _ ->
+            assertThat(db.daoMethods.size, `is`(1))
+            assertThat(db.entities.size, `is`(1))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun multiple() {
+        singleDb("""
+            @Database(entities = {User.class, Book.class}, version = 42)
+            public abstract class MyDb extends RoomDatabase {
+                abstract UserDao userDao();
+                abstract BookDao bookDao();
+            }
+            """, USER, USER_DAO, BOOK, BOOK_DAO) { db, _ ->
+            assertThat(db.daoMethods.size, `is`(2))
+            assertThat(db.entities.size, `is`(2))
+            assertThat(db.daoMethods.map { it.name }, `is`(listOf("userDao", "bookDao")))
+            assertThat(db.entities.map { it.type.toString() },
+                    `is`(listOf("foo.bar.User", "foo.bar.Book")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun detectMissingBaseClass() {
+        singleDb("""
+            @Database(entities = {User.class, Book.class}, version = 42)
+            public abstract class MyDb {
+            }
+            """, USER, BOOK) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
+    }
+
+    @Test
+    fun detectMissingTable() {
+        singleDb(
+                """
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Dao
+                public interface BookDao {
+                    @Query("SELECT * FROM nonExistentTable")
+                    public java.util.List<Book> loadAllBooks();
+                }
+                """)) { _, _ ->
+        }.failsToCompile().withErrorContaining("no such table: nonExistentTable")
+    }
+
+    @Test
+    fun detectDuplicateTableNames() {
+        singleDb("""
+                @Database(entities = {User.class, AnotherClass.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract UserDao userDao();
+                }
+                """, USER, USER_DAO, JavaFileObjects.forSourceString("foo.bar.AnotherClass",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(tableName="user")
+                public class AnotherClass {
+                    @PrimaryKey
+                    int uid;
+                }
+                """)) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.duplicateTableNames("user",
+                        listOf("foo.bar.User", "foo.bar.AnotherClass"))
+        )
+    }
+
+    @Test
+    fun skipBadQueryVerification() {
+        singleDb(
+                """
+                @SkipQueryVerification
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Dao
+                public interface BookDao {
+                    @Query("SELECT nonExistingField FROM Book")
+                    public java.util.List<Book> loadAllBooks();
+                }
+                """)) { _, _ ->
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun multipleDatabases() {
+        val db1_2 = JavaFileObjects.forSourceString("foo.barx.Db1",
+                """
+                package foo.barx;
+                import androidx.room.*;
+                import foo.bar.*;
+                @Database(entities = {Book.class}, version = 42)
+                public abstract class Db1 extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """)
+        Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2))
+                .processedWith(RoomProcessor())
+                .compilesWithoutError()
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db1_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db2_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.barx", "Db1_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
+                        "BookDao_Db1_0_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
+                        "BookDao_Db1_1_Impl.class")
+                .and()
+                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
+                        "BookDao_Db2_Impl.class")
+    }
+
+    @Test
+    fun twoDaoMethodsForTheSameDao() {
+        singleDb(
+                """
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract UserDao userDao();
+                    abstract UserDao userDao2();
+                }
+                """, USER, USER_DAO) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
+                .and()
+                .withErrorContaining(ProcessorErrors.duplicateDao(
+                        ClassName.get("foo.bar", "UserDao"), listOf("userDao", "userDao2")
+                ))
+    }
+
+    @Test
+    fun suppressedWarnings() {
+        singleDb(
+                """
+                @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract UserDao userDao();
+                }
+                """, USER, USER_DAO) { db, invocation ->
+            assertThat(DatabaseProcessor(invocation.context, db.element)
+                    .context.logger.suppressedWarnings, `is`(setOf(Warning.CURSOR_MISMATCH)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun duplicateIndexNames() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(indices = {@Index(name ="index_name", value = {"name"})})
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(indices = {@Index(name ="index_name", value = {"anotherName"})})
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.duplicateIndexInDatabase("index_name",
+                        listOf("foo.bar.Entity1 > index_name", "foo.bar.Entity2 > index_name"))
+        )
+    }
+
+    @Test
+    fun foreignKey_missingParent() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
+                        parentColumns = "lastName",
+                        childColumns = "name"))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, COMMON.USER) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.foreignKeyMissingParentEntityInDatabase("User", "foo.bar.Entity1")
+        )
+    }
+
+    @Test
+    fun foreignKey_missingParentIndex() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
+                        parentColumns = "lastName",
+                        childColumns = "name"))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, COMMON.USER) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.foreignKeyMissingIndexInParent(
+                        parentEntity = COMMON.USER_TYPE_NAME.toString(),
+                        parentColumns = listOf("lastName"),
+                        childEntity = "foo.bar.Entity1",
+                        childColumns = listOf("name")
+                )
+        )
+    }
+
+    @Test
+    fun foreignKey_goodWithPrimaryKey() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
+                    parentColumns = "uid",
+                    childColumns = "parentId"))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    int parentId;
+                    String name;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2) { _, _ ->
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun foreignKey_missingParentColumn() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
+                    parentColumns = {"anotherName", "anotherName2"},
+                    childColumns = {"name", "name2"}))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                    String name2;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.foreignKeyParentColumnDoesNotExist("foo.bar.Entity2",
+                        "anotherName2", listOf("uid", "anotherName"))
+        )
+    }
+
+    @Test
+    fun foreignKey_goodWithIndex() {
+        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
+                    parentColumns = {"anotherName", "anotherName2"},
+                    childColumns = {"name", "name2"}))
+                public class Entity1 {
+                    @PrimaryKey
+                    int uid;
+                    String name;
+                    String name2;
+                }
+                """)
+
+        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(indices = @Index(value = {"anotherName2", "anotherName"}, unique = true))
+                public class Entity2 {
+                    @PrimaryKey
+                    int uid;
+                    String anotherName;
+                    String anotherName2;
+                }
+                """)
+        singleDb("""
+                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                }
+                """, entity1, entity2) { _, _ ->
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertNotAReferencedEntity() {
+        singleDb("""
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    abstract BookDao bookDao();
+                }
+                """, USER, USER_DAO, BOOK, BOOK_DAO) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.shortcutEntityIsNotInDatabase(
+                        database = "foo.bar.MyDb",
+                        dao = "foo.bar.BookDao",
+                        entity = "foo.bar.Book"
+                )
+        )
+    }
+
+    @Test
+    fun cache_entity() {
+        singleDb("""
+                @Database(entities = {User.class}, version = 42)
+                @SkipQueryVerification
+                public abstract class MyDb extends RoomDatabase {
+                    public abstract MyUserDao userDao();
+                    @Dao
+                    interface MyUserDao {
+                        @Insert
+                        public void insert(User... users);
+
+                        @Query("SELECT * FROM user where uid = :uid")
+                        public User loadOne(int uid);
+
+                        @TypeConverters(Converter.class)
+                        @Query("SELECT * FROM user where uid = :uid")
+                        public User loadWithConverter(int uid);
+                    }
+                    public static class Converter {
+                        @TypeConverter
+                        public static java.util.Date foo(Long input) {return null;}
+                    }
+                }
+                """, USER, USER_DAO) { db, _ ->
+            val userDao = db.daoMethods.first().dao
+            val insertionMethod = userDao.insertionMethods.find { it.name == "insert" }
+            assertThat(insertionMethod, notNullValue())
+            val loadOne = userDao.queryMethods.find { it.name == "loadOne" }
+            assertThat(loadOne, notNullValue())
+            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
+            val adapterEntity = (adapter as EntityRowAdapter).entity
+            assertThat(insertionMethod?.entities?.values?.first(), sameInstance(adapterEntity))
+
+            val withConverter = userDao.queryMethods.find { it.name == "loadWithConverter" }
+            assertThat(withConverter, notNullValue())
+            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
+            val convAdapterEntity = (convAdapter as EntityRowAdapter).entity
+            assertThat(insertionMethod?.entities?.values?.first(),
+                    not(sameInstance(convAdapterEntity)))
+
+            assertThat(convAdapterEntity, notNullValue())
+            assertThat(adapterEntity, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun cache_pojo() {
+        singleDb("""
+                @Database(entities = {User.class}, version = 42)
+                public abstract class MyDb extends RoomDatabase {
+                    public abstract UserDao userDao();
+                }
+                """, USER, USER_DAO) { db, _ ->
+            val userDao = db.daoMethods.first().dao
+            val loadOne = userDao.queryMethods.find { it.name == "loadOnePojo" }
+            assertThat(loadOne, notNullValue())
+            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
+            val adapterPojo = (adapter as PojoRowAdapter).pojo
+
+            val loadAll = userDao.queryMethods.find { it.name == "loadAllPojos" }
+            assertThat(loadAll, notNullValue())
+            val loadAllAdapter = loadAll?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", loadAllAdapter, instanceOf(PojoRowAdapter::class.java))
+            val loadAllPojo = (loadAllAdapter as PojoRowAdapter).pojo
+            assertThat(adapter, not(sameInstance(loadAllAdapter)))
+            assertThat(adapterPojo, sameInstance(loadAllPojo))
+
+            val withConverter = userDao.queryMethods.find { it.name == "loadPojoWithConverter" }
+            assertThat(withConverter, notNullValue())
+            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
+            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
+            val convAdapterPojo = (convAdapter as PojoRowAdapter).pojo
+            assertThat(convAdapterPojo, notNullValue())
+            assertThat(convAdapterPojo, not(sameInstance(adapterPojo)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_RoomDatabase() {
+        assertConstructor(listOf(DB1), "BookDao(RoomDatabase db) {}")
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_specificDatabase() {
+        assertConstructor(listOf(DB1), "BookDao(Db1 db) {}")
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_wrongDatabase() {
+        assertConstructor(listOf(DB1, DB3), "BookDao(Db3 db) {}")
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors
+                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db1"))
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_RoomDatabase() {
+        assertConstructor(listOf(DB1, DB2), "BookDao(RoomDatabase db) {}")
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_specificDatabases() {
+        assertConstructor(listOf(DB1, DB2), """
+                    BookDao(Db1 db) {}
+                    BookDao(Db2 db) {}
+                """)
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_empty() {
+        assertConstructor(listOf(DB1, DB2), """
+                    BookDao(Db1 db) {}
+                    BookDao() {} // Db2 uses this
+                """)
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun daoConstructor_multipleDatabases_noMatch() {
+        assertConstructor(listOf(DB1, DB2), """
+                    BookDao(Db1 db) {}
+                """)
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors
+                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db2"))
+    }
+
+    fun assertConstructor(dbs: List<JavaFileObject>, constructor: String): CompileTester {
+        val bookDao = JavaFileObjects.forSourceString("foo.bar.BookDao",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Dao
+                public abstract class BookDao {
+                    $constructor
+                }
+                """)
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(BOOK, bookDao) + dbs)
+                .processedWith(RoomProcessor())
+    }
+
+    fun singleDb(input: String, vararg otherFiles: JavaFileObject,
+                 handler: (Database, TestInvocation) -> Unit): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(otherFiles.toMutableList()
+                        + JavaFileObjects.forSourceString("foo.bar.MyDb",
+                        DATABASE_PREFIX + input
+                ))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(androidx.room.Database::class)
+                        .nextRunHandler { invocation ->
+                            val entity = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            androidx.room.Database::class.java)
+                                    .first()
+                            val parser = DatabaseProcessor(invocation.context,
+                                    MoreElements.asType(entity))
+                            val parsedDb = parser.process()
+                            handler(parsedDb, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt
new file mode 100644
index 0000000..36f0e4a
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DeletionMethodProcessorTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.processor
+
+import androidx.room.Delete
+import androidx.room.processor.ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+import androidx.room.processor.ProcessorErrors.DELETION_MISSING_PARAMS
+import androidx.room.vo.DeletionMethod
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class DeletionMethodProcessorTest : ShortcutMethodProcessorTest<DeletionMethod>(Delete::class) {
+    override fun invalidReturnTypeError(): String = DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+
+    override fun noParamsError(): String = DELETION_MISSING_PARAMS
+
+    override fun process(baseContext: Context, containing: DeclaredType,
+                         executableElement: ExecutableElement): DeletionMethod {
+        return DeletionMethodProcessor(baseContext, containing, executableElement).process()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/EntityNameMatchingVariationsTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/EntityNameMatchingVariationsTest.kt
new file mode 100644
index 0000000..ad7e072
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/EntityNameMatchingVariationsTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.vo.CallType
+import androidx.room.vo.Field
+import androidx.room.vo.FieldGetter
+import androidx.room.vo.FieldSetter
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import javax.lang.model.type.TypeKind.INT
+
+@RunWith(Parameterized::class)
+class EntityNameMatchingVariationsTest(triple: Triple<String, String, String>) :
+        BaseEntityParserTest() {
+    val fieldName = triple.first
+    val getterName = triple.second
+    val setterName = triple.third
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun params(): List<Triple<String, String, String>> {
+            val result = arrayListOf<Triple<String, String, String>>()
+            arrayListOf("x", "_x", "mX").forEach { field ->
+                arrayListOf("getX", "x").forEach { getter ->
+                    arrayListOf("setX", "x").forEach { setter ->
+                        result.add(Triple(field, getter, setter))
+                    }
+                }
+            }
+            return result
+        }
+    }
+
+    @Test
+    fun testSuccessfulParamToMethodMatching() {
+        singleEntity("""
+                @PrimaryKey
+                private int $fieldName;
+                public int $getterName() { return $fieldName; }
+                public void $setterName(int id) { this.$fieldName = id; }
+            """) { entity, invocation ->
+            assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
+            assertThat(entity.fields.size, `is`(1))
+            val field = entity.fields.first()
+            val intType = invocation.processingEnv.typeUtils.getPrimitiveType(INT)
+            assertThat(field, `is`(Field(
+                    element = field.element,
+                    name = fieldName,
+                    type = intType,
+                    columnName = fieldName,
+                    affinity = SQLTypeAffinity.INTEGER)))
+            assertThat(field.setter, `is`(FieldSetter(setterName, intType, CallType.METHOD)))
+            assertThat(field.getter, `is`(FieldGetter(getterName, intType, CallType.METHOD)))
+        }.compilesWithoutError()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/EntityProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/EntityProcessorTest.kt
new file mode 100644
index 0000000..69ada7c
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/EntityProcessorTest.kt
@@ -0,0 +1,1907 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import COMMON
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import androidx.room.vo.CallType
+import androidx.room.vo.Field
+import androidx.room.vo.FieldGetter
+import androidx.room.vo.FieldSetter
+import androidx.room.vo.Index
+import androidx.room.vo.Pojo
+import com.google.testing.compile.JavaFileObjects
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.type.TypeKind.INT
+
+@RunWith(JUnit4::class)
+class EntityProcessorTest : BaseEntityParserTest() {
+    @Test
+    fun simple() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public int getId() { return id; }
+                public void setId(int id) { this.id = id; }
+            """) { entity, invocation ->
+            assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
+            assertThat(entity.fields.size, `is`(1))
+            val field = entity.fields.first()
+            val intType = invocation.processingEnv.typeUtils.getPrimitiveType(INT)
+            assertThat(field, `is`(Field(
+                    element = field.element,
+                    name = "id",
+                    type = intType,
+                    columnName = "id",
+                    affinity = SQLTypeAffinity.INTEGER)))
+            assertThat(field.setter, `is`(FieldSetter("setId", intType, CallType.METHOD)))
+            assertThat(field.getter, `is`(FieldGetter("getId", intType, CallType.METHOD)))
+            assertThat(entity.primaryKey.fields, `is`(listOf(field)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun noGetter() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {this.id = id;}
+                """) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
+    }
+
+    @Test
+    fun getterWithBadType() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public float getId() {return 0f;}
+                public void setId(int id) {this.id = id;}
+                """) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
+    }
+
+    @Test
+    fun setterWithBadType() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public int getId() {return id;}
+                public void setId(float id) {}
+                """) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
+    }
+
+    @Test
+    fun setterWithAssignableType() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public int getId() {return id;}
+                public void setId(Integer id) {}
+                """) { _, _ -> }
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun getterWithAssignableType() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public Integer getId() {return id;}
+                public void setId(int id) {}
+                """) { _, _ -> }
+                .compilesWithoutError()
+    }
+
+    @Test
+    fun noSetter() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public int getId(){ return id; }
+                """) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
+    }
+
+    @Test
+    fun tooManyGetters() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                public int id(){ return id; }
+                """) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining("getId, id")
+    }
+
+    @Test
+    fun tooManyGettersWithIgnore() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                @Ignore public int id(){ return id; }
+                """) { entity, _ ->
+            assertThat(entity.fields.first().getter.name, `is`("getId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManyGettersWithDifferentVisibility() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                protected int id(){ return id; }
+                """) { entity, _ ->
+            assertThat(entity.fields.first().getter.name, `is`("getId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManyGettersWithDifferentTypes() {
+        singleEntity("""
+                @PrimaryKey
+                public int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                """) { entity, _ ->
+            assertThat(entity.fields.first().getter.name, `is`("id"))
+            assertThat(entity.fields.first().getter.callType, `is`(CallType.FIELD))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManySetters() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                public void id(int id) {}
+                public int getId(){ return id; }
+                """) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining("setId, id")
+    }
+
+    @Test
+    fun tooManySettersWithIgnore() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                @Ignore public void id(int id) {}
+                public int getId(){ return id; }
+                """) { entity, _ ->
+            assertThat(entity.fields.first().setter.name, `is`("setId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManySettersWithDifferentVisibility() {
+        singleEntity("""
+                @PrimaryKey
+                private int id;
+                public void setId(int id) {}
+                protected void id(int id) {}
+                public int getId(){ return id; }
+                """) { entity, _ ->
+            assertThat(entity.fields.first().setter.name, `is`("setId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun tooManySettersWithDifferentTypes() {
+        singleEntity("""
+                @PrimaryKey
+                public int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                """) { entity, _ ->
+            assertThat(entity.fields.first().setter.name, `is`("id"))
+            assertThat(entity.fields.first().setter.callType, `is`(CallType.FIELD))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun preferPublicOverProtected() {
+        singleEntity("""
+                @PrimaryKey
+                int id;
+                public void setId(int id) {}
+                public int getId(){ return id; }
+                """) { entity, _ ->
+            assertThat(entity.fields.first().setter.name, `is`("setId"))
+            assertThat(entity.fields.first().getter.name, `is`("getId"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun customName() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                """, hashMapOf(Pair("tableName", "\"foo_table\""))) { entity, _ ->
+            assertThat(entity.tableName, `is`("foo_table"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun emptyCustomName() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                """, hashMapOf(Pair("tableName", "\" \""))) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
+    }
+
+    @Test
+    fun missingPrimaryKey() {
+        singleEntity("""
+                """) { _, _ ->
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.MISSING_PRIMARY_KEY)
+    }
+
+    @Test
+    fun missingColumnAdapter() {
+        singleEntity("""
+                @PrimaryKey
+                public java.util.Date myDate;
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.CANNOT_FIND_COLUMN_TYPE_ADAPTER)
+    }
+
+    @Test
+    fun dropSubPrimaryKey() {
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                Point myPoint;
+                static class Point {
+                    @PrimaryKey
+                    int x;
+                    int y;
+                }
+                """
+        ) { entity, _ ->
+            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
+        }.compilesWithoutError()
+                .withWarningCount(1)
+                .withWarningContaining(ProcessorErrors.embeddedPrimaryKeyIsDropped(
+                        "foo.bar.MyEntity", "x"))
+    }
+
+    @Test
+    fun ignoreDropSubPrimaryKey() {
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
+                Point myPoint;
+                static class Point {
+                    @PrimaryKey
+                    int x;
+                    int y;
+                }
+                """
+        ) { entity, _ ->
+            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun notNull() {
+        singleEntity(
+                """
+                @PrimaryKey int id;
+                @NonNull public String name;
+                """
+        ) { entity, _ ->
+            val field = fieldsByName(entity, "name").first()
+            assertThat(field.name, `is`("name"))
+            assertThat(field.columnName, `is`("name"))
+            assertThat(field.nonNull, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    private fun fieldsByName(entity: Pojo, vararg fieldNames: String): List<Field> {
+        return fieldNames
+                .map { name -> entity.fields.find { it.name == name } }
+                .filterNotNull()
+    }
+
+    @Test
+    fun index_simple() {
+        val annotation = mapOf(
+                "indices" to """@Index("foo")"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, _ ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_fromField() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @ColumnInfo(index = true)
+                public String foo;
+                """) { entity, _ ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_multiColumn() {
+        val annotation = mapOf(
+                "indices" to """@Index({"foo", "id"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, _ ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo_id",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo", "id")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_multiple() {
+        val annotation = mapOf(
+                "indices" to """{@Index({"foo", "id"}), @Index({"bar_column", "foo"})}"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                @ColumnInfo(name = "bar_column")
+                public String bar;
+                """
+                , annotation) { entity, _ ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyEntity_foo_id",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo", "id")),
+                            Index(name = "index_MyEntity_bar_column_foo",
+                                    unique = false,
+                                    fields = fieldsByName(entity, "bar", "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_unique() {
+        val annotation = mapOf(
+                "indices" to """@Index(value = {"foo", "id"}, unique = true)"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, _ ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(
+                            name = "index_MyEntity_foo_id",
+                            unique = true,
+                            fields = fieldsByName(entity, "foo", "id")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_customName() {
+        val annotation = mapOf(
+                "indices" to """@Index(value = {"foo"}, name = "myName")"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, _ ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "myName",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_customTableName() {
+        val annotation = mapOf(
+                "tableName" to "\"MyTable\"",
+                "indices" to """@Index(value = {"foo"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { entity, _ ->
+            assertThat(entity.indices, `is`(
+                    listOf(Index(name = "index_MyTable_foo",
+                            unique = false,
+                            fields = fieldsByName(entity, "foo")))
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun index_empty() {
+        val annotation = mapOf(
+                "indices" to """@Index({})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
+        )
+    }
+
+    @Test
+    fun index_missingColumn() {
+        val annotation = mapOf(
+                "indices" to """@Index({"foo", "bar"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                public String foo;
+                """
+                , annotation) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.indexColumnDoesNotExist("bar", listOf("id, foo"))
+        )
+    }
+
+    @Test
+    fun index_nameConflict() {
+        val annotation = mapOf(
+                "indices" to """@Index({"foo"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @ColumnInfo(index = true)
+                public String foo;
+                """
+                , annotation) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.duplicateIndexInEntity("index_MyEntity_foo")
+        )
+    }
+
+    @Test
+    fun index_droppedParentFieldIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    @ColumnInfo(index = true)
+                    String name;
+                    String lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """, baseClass = "foo.bar.Base", jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedSuperClassFieldIndex(
+                                fieldName = "name",
+                                childEntity = "foo.bar.MyEntity",
+                                superEntity = "foo.bar.Base")
+                )
+    }
+
+    @Test
+    fun index_keptGrandParentEntityIndex() {
+        val grandParent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Parent",
+                """
+                package foo.bar;
+                import androidx.room.*;
+
+                public class Parent extends Base {
+                    String iHaveAField;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Parent",
+                attributes = hashMapOf("inheritSuperIndices" to "true"),
+                jfos = listOf(parent, grandParent)) {
+            entity, _ ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(),
+                    `is`(Index(name = "index_MyEntity_name_lastName",
+                            unique = false,
+                            fields = fieldsByName(entity, "name", "lastName"))))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun index_keptParentEntityIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                attributes = hashMapOf("inheritSuperIndices" to "true"),
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(),
+                    `is`(Index(name = "index_MyEntity_name_lastName",
+                            unique = false,
+                            fields = fieldsByName(entity, "name", "lastName"))))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun index_keptParentFieldIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    @ColumnInfo(index = true)
+                    String name;
+                    String lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                attributes = hashMapOf("inheritSuperIndices" to "true"),
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(),
+                    `is`(Index(name = "index_MyEntity_name",
+                            unique = false,
+                            fields = fieldsByName(entity, "name"))))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun index_droppedGrandParentEntityIndex() {
+        val grandParent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Parent",
+                """
+                package foo.bar;
+                import androidx.room.*;
+
+                public class Parent extends Base {
+                    String iHaveAField;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """, baseClass = "foo.bar.Parent", jfos = listOf(parent, grandParent)) {
+            entity, _ ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedSuperClassIndex(
+                                childEntity = "foo.bar.MyEntity",
+                                superEntity = "foo.bar.Base")
+                )
+    }
+
+    @Test
+    fun index_droppedParentEntityIndex() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(indices = @Index({"name", "lastName"}))
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """, baseClass = "foo.bar.Base", jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedSuperClassIndex(
+                                childEntity = "foo.bar.MyEntity",
+                                superEntity = "foo.bar.Base")
+                )
+    }
+
+    @Test
+    fun index_droppedEmbeddedEntityIndex() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                public Foo foo;
+                @Entity(indices = {@Index("a")})
+                static class Foo {
+                    @PrimaryKey
+                    @ColumnInfo(name = "foo_id")
+                    int id;
+                    @ColumnInfo(index = true)
+                    public int a;
+                }
+                """) { entity, _ ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedEmbeddedIndex(
+                                entityName = "foo.bar.MyEntity.Foo",
+                                fieldPath = "foo",
+                                grandParent = "foo.bar.MyEntity")
+                )
+    }
+
+    @Test
+    fun index_onEmbeddedField() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                @ColumnInfo(index = true)
+                public Foo foo;
+                static class Foo {
+                    @ColumnInfo(index = true)
+                    public int a;
+                }
+                """) { entity, _ ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION
+        )
+    }
+
+    @Test
+    fun index_droppedEmbeddedFieldIndex() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                public Foo foo;
+                static class Foo {
+                    @ColumnInfo(index = true)
+                    public int a;
+                }
+                """) { entity, _ ->
+            assertThat(entity.indices.isEmpty(), `is`(true))
+        }.compilesWithoutError()
+                .withWarningContaining(
+                        ProcessorErrors.droppedEmbeddedFieldIndex("foo > a", "foo.bar.MyEntity")
+                )
+    }
+
+    @Test
+    fun index_referenceEmbeddedField() {
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                @Embedded
+                public Foo foo;
+                static class Foo {
+                    public int a;
+                }
+                """, attributes = mapOf("indices" to "@Index(\"a\")")) { entity, _ ->
+            assertThat(entity.indices.size, `is`(1))
+            assertThat(entity.indices.first(), `is`(
+                    Index(
+                            name = "index_MyEntity_a",
+                            unique = false,
+                            fields = fieldsByName(entity, "a")
+                    )
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primaryKey_definedInBothWays() {
+        singleEntity(
+                """
+                public int id;
+                @PrimaryKey
+                public String foo;
+                """,
+                attributes = mapOf("primaryKeys" to "\"id\"")) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.multiplePrimaryKeyAnnotations(
+                        listOf("PrimaryKey[id]", "PrimaryKey[foo]")
+                ))
+    }
+
+    @Test
+    fun primaryKey_badColumnName() {
+        singleEntity(
+                """
+                public int id;
+                """,
+                attributes = mapOf("primaryKeys" to "\"foo\"")) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.primaryKeyColumnDoesNotExist("foo", listOf("id")))
+    }
+
+    @Test
+    fun primaryKey_multipleAnnotations() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                @PrimaryKey
+                int y;
+                """) { entity, _ ->
+            assertThat(entity.primaryKey.fields.isEmpty(), `is`(true))
+        }.failsToCompile()
+                .withErrorContaining(
+                        ProcessorErrors.multiplePrimaryKeyAnnotations(
+                                listOf("PrimaryKey[x]", "PrimaryKey[y]")))
+    }
+
+    @Test
+    fun primaryKey_fromParentField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_fromParentEntity() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("baseId"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                public class Base {
+                    @PrimaryKey
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentEntityViaField() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_overrideFromParentEntityViaEntity() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent),
+                attributes = mapOf("primaryKeys" to "\"id\"")) { entity, _ ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+            assertThat(entity.primaryKey.autoGenerateId, `is`(false))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[id]"
+        )
+    }
+
+    @Test
+    fun primaryKey_autoGenerate() {
+        listOf("long", "Long", "Integer", "int").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                public $type id;
+                """) { entity, _ ->
+                assertThat(entity.primaryKey.fields.size, `is`(1))
+                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun primaryKey_nonNull_notNeeded() {
+        listOf("long", "Long", "Integer", "int").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey
+                public $type id;
+                """) { entity, _ ->
+                assertThat(entity.primaryKey.fields.size, `is`(1))
+                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+                assertThat(entity.primaryKey.autoGenerateId, `is`(false))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun primaryKey_autoGenerateBadType() {
+        listOf("String", "float", "Float", "Double", "double").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                public $type id;
+                """) { entity, _ ->
+                assertThat(entity.primaryKey.fields.size, `is`(1))
+                assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+                assertThat(entity.primaryKey.autoGenerateId, `is`(true))
+            }.failsToCompile().withErrorContaining(
+                    ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT)
+        }
+    }
+
+    @Test
+    fun primaryKey_embedded() {
+        singleEntity(
+                """
+                public int id;
+
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                @NonNull
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """) { entity, _ ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_embeddedInherited() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    @NonNull
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun primaryKey_overrideViaEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                @NonNull
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("bar_a", "bar_b")))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[foo > a, foo > b]")
+    }
+
+    @Test
+    fun primaryKey_overrideEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    @NonNull
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { entity, _ ->
+            assertThat(entity.primaryKey.fields.map { it.columnName },
+                    `is`(listOf("id")))
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[foo > a, foo > b] is overridden by PrimaryKey[id]")
+    }
+
+    @Test
+    fun primaryKey_NonNull() {
+        singleEntity(
+                """
+            @PrimaryKey
+            @NonNull
+            public String id;
+            """) { entity, _ ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primaryKey_Nullable() {
+        singleEntity(
+                """
+            @PrimaryKey
+            public String id;
+            """) { entity, _ ->
+            assertThat(entity.primaryKey.fields.size, `is`(1))
+            assertThat(entity.primaryKey.fields.firstOrNull()?.name, `is`("id"))
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("id"))
+    }
+
+    @Test
+    fun primaryKey_MultipleNullable() {
+        singleEntity(
+                """
+            @PrimaryKey
+            public String id;
+            @PrimaryKey
+            public String anotherId;
+            """) { entity, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("id"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("anotherId"))
+    }
+
+    @Test
+    fun primaryKey_MultipleNullableAndNonNullable() {
+        singleEntity(
+                """
+            @PrimaryKey
+            @NonNull
+            public String id;
+            @PrimaryKey
+            public String anotherId;
+            """) { entity, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("anotherId"))
+    }
+
+    @Test
+    fun primaryKey_definedAsAttributesNullable() {
+        singleEntity(
+                """
+                public int id;
+                public String foo;
+                """,
+                attributes = mapOf("primaryKeys" to "{\"id\", \"foo\"}")) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+    }
+
+    @Test
+    fun primaryKey_definedAsAttributesNonNull() {
+        singleEntity(
+                """
+                public int id;
+                @NonNull
+                public String foo;
+                """,
+                attributes = mapOf("primaryKeys" to "{\"id\", \"foo\"}")) { entity, _ ->
+            assertThat(entity.primaryKey.fields.map { it.name }, `is`(listOf("id", "foo")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primaryKey_nullableEmbedded() {
+        singleEntity(
+                """
+                public int id;
+
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+    }
+
+    @Test
+    fun primaryKey_nullableEmbeddedObject() {
+        singleEntity(
+                """
+                public int id;
+
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                    public String a;
+                    public String b;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+                .and().withErrorCount(3)
+    }
+
+    @Test
+    fun primaryKey_nullableEmbeddedObject_multipleParents() {
+        singleEntity(
+                """
+                public int id;
+
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                @Embedded(prefix = "baz_")
+                public Baz a;
+                public String b;
+
+                static class Baz {
+                    public Integer bb;
+                }
+            }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a > bb"))
+                .and().withErrorCount(4)
+    }
+
+    @Test
+    fun primaryKey_nullableEmbeddedInherited() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
+                .and().withErrorCount(3)
+    }
+
+    @Test
+    fun primaryKey_nullableOverrideViaEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.room.*;
+
+                @Entity(primaryKeys = "baseId")
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                }
+                """)
+        singleEntity(
+                """
+                public int id;
+                @Embedded(prefix = "bar_")
+                @PrimaryKey
+                public Foo foo;
+
+                static class Foo {
+                    public int a;
+                    public int b;
+                }
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
+                .and().withNoteContaining(
+                "PrimaryKey[baseId] is overridden by PrimaryKey[foo > a, foo > b]")
+                .and().withErrorCount(3)
+    }
+
+    @Test
+    fun primaryKey_nullableOverrideEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public int a;
+                        public int b;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > b"))
+                .and().withNoteContaining(
+                "PrimaryKey[foo > a, foo > b] is overridden by PrimaryKey[id]")
+                .and().withErrorCount(3)
+    }
+
+    @Test
+    fun primaryKey_integerOverrideEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public Integer a;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { _, _ ->
+        }.compilesWithoutError().withNoteContaining(
+                "PrimaryKey[foo > a] is overridden by PrimaryKey[id]")
+    }
+
+    @Test
+    fun primaryKey_singleStringPrimaryKeyOverrideEmbedded() {
+        val parent = JavaFileObjects.forSourceLines("foo.bar.Base",
+                """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+
+                public class Base {
+                    long baseId;
+                    String name, lastName;
+                    @Embedded(prefix = "bar_")
+                    @PrimaryKey
+                    public Foo foo;
+
+                    static class Foo {
+                        public String a;
+                    }
+                }
+                """)
+        singleEntity(
+                """
+                @PrimaryKey
+                public int id;
+                """,
+                baseClass = "foo.bar.Base",
+                jfos = listOf(parent)) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.primaryKeyNull("foo"))
+                .and().withErrorContaining(ProcessorErrors.primaryKeyNull("foo > a"))
+                .and().withNoteContaining(
+                "PrimaryKey[foo > a] is overridden by PrimaryKey[id]")
+                .and().withErrorCount(2)
+    }
+
+    @Test
+    fun relationInEntity() {
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                java.util.List<User> users;
+                """, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(RELATION_IN_ENTITY)
+    }
+
+    @Test
+    fun foreignKey_invalidAction() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = 101
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_FOREIGN_KEY_ACTION)
+    }
+
+    @Test
+    fun foreignKey_badEntity() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = dsa.class,
+                    parentColumns = "lastName",
+                    childColumns = "name"
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining("cannot find symbol")
+    }
+
+    @Test
+    fun foreignKey_notAnEntity() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name"
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.NOT_AN_ENTITY)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyNotAnEntity(
+                COMMON.NOT_AN_ENTITY_TYPE_NAME.toString()))
+    }
+
+    @Test
+    fun foreignKey_invalidChildColumn() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "namex"
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyChildColumnDoesNotExist(
+                "namex", listOf("id", "name")))
+    }
+
+    @Test
+    fun foreignKey_columnCountMismatch() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = {"name", "id"}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.foreignKeyColumnNumberMismatch(
+                listOf("name", "id"), listOf("lastName")))
+    }
+
+    @Test
+    fun foreignKey_emptyChildColumns() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = {}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST)
+    }
+
+    @Test
+    fun foreignKey_emptyParentColumns() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {},
+                    childColumns = {"name"}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST)
+    }
+
+    @Test
+    fun foreignKey_simple() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, _ ->
+            assertThat(entity.foreignKeys.size, `is`(1))
+            val fKey = entity.foreignKeys.first()
+            assertThat(fKey.parentTable, `is`("User"))
+            assertThat(fKey.parentColumns, `is`(listOf("lastName")))
+            assertThat(fKey.deferred, `is`(true))
+            assertThat(fKey.childFields.size, `is`(1))
+            val field = fKey.childFields.first()
+            assertThat(field.name, `is`("name"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun foreignKey_dontDuplicationChildIndex_SingleColumn() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent(),
+                "indices" to """@Index("name")"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.compilesWithoutWarnings()
+    }
+
+    @Test
+    fun foreignKey_dontDuplicationChildIndex_MultipleColumns() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {"lastName", "name"},
+                    childColumns = {"lName", "name"},
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent(),
+                "indices" to """@Index({"lName", "name"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                String lName;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, _ ->
+            assertThat(entity.indices.size, `is`(1))
+        }.compilesWithoutWarnings()
+    }
+
+    @Test
+    fun foreignKey_dontDuplicationChildIndex_WhenCovered() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {"lastName"},
+                    childColumns = {"name"},
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent(),
+                "indices" to """@Index({"name", "lName"})"""
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                String lName;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, _ ->
+            assertThat(entity.indices.size, `is`(1))
+        }.compilesWithoutWarnings()
+    }
+
+    @Test
+    fun foreignKey_warnMissingChildIndex() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "name",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, _ ->
+            assertThat(entity.indices, `is`(emptyList()))
+        }.compilesWithoutError().withWarningContaining(
+                ProcessorErrors.foreignKeyMissingIndexInChildColumn("name"))
+    }
+
+    @Test
+    fun foreignKey_warnMissingChildrenIndex() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = {"lastName", "name"},
+                    childColumns = {"lName", "name"}
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                String lName;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, _ ->
+            assertThat(entity.indices, `is`(emptyList()))
+        }.compilesWithoutError().withWarningContaining(
+                ProcessorErrors.foreignKeyMissingIndexInChildColumns(listOf("lName", "name")))
+    }
+
+    @Test
+    fun foreignKey_dontIndexIfAlreadyPrimaryKey() {
+        val annotation = mapOf(
+                "foreignKeys" to """{@ForeignKey(
+                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    parentColumns = "lastName",
+                    childColumns = "id",
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE,
+                    deferred = true
+                )}""".trimIndent()
+        )
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { entity, _ ->
+            assertThat(entity.indices, `is`(emptyList()))
+        }.compilesWithoutWarnings()
+    }
+
+    @Test
+    fun recursion_1Level() {
+        singleEntity(
+                """
+                @Embedded
+                MyEntity myEntity;
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyEntity -> foo.bar.MyEntity"))
+    }
+
+    @Test
+    fun recursion_2Levels_embedToRelation() {
+        singleEntity(
+                """
+                int pojoId;
+                @Embedded
+                A a;
+
+                static class A {
+                    int entityId;
+                    @Relation(parentColumn = "pojoId", entityColumn = "entityId")
+                    MyEntity myEntity;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
+    }
+
+    @Test
+    fun recursion_2Levels_onlyEmbeds_entityToPojo() {
+        singleEntity(
+                """
+                @Embedded
+                A a;
+
+                static class A {
+                    @Embedded
+                    MyEntity myEntity;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
+    }
+
+    @Test
+    fun recursion_2Levels_onlyEmbeds_onlyEntities() {
+        singleEntity(
+                """
+                @Embedded
+                A a;
+
+                @Entity
+                static class A {
+                    @Embedded
+                    MyEntity myEntity;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyEntity -> foo.bar.MyEntity.A -> foo.bar.MyEntity"))
+    }
+    fun okTableName() {
+        val annotation = mapOf("tableName" to "\"foo bar\"")
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun badTableName() {
+        val annotation = mapOf("tableName" to """ "foo`bar" """)
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                String name;
+                """,
+                attributes = annotation, jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_TABLE_NAME)
+    }
+
+    @Test
+    fun badColumnName() {
+        singleEntity(
+                """
+                @PrimaryKey
+                int id;
+                @ColumnInfo(name = "\"foo bar\"")
+                String name;
+                """,
+                jfos = listOf(COMMON.USER)
+        ) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_COLUMN_NAME)
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
new file mode 100644
index 0000000..f539de6
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import androidx.room.Entity
+import androidx.room.parser.Collate
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.solver.types.ColumnTypeAdapter
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.Field
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import simpleRun
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@Suppress("HasPlatformType")
+@RunWith(JUnit4::class)
+class FieldProcessorTest {
+    companion object {
+        const val ENTITY_PREFIX = """
+                package foo.bar;
+                import androidx.room.*;
+                @Entity
+                abstract class MyEntity {
+                """
+        const val ENTITY_SUFFIX = "}"
+        val ALL_PRIMITIVES = arrayListOf(
+                TypeKind.INT,
+                TypeKind.BYTE,
+                TypeKind.SHORT,
+                TypeKind.LONG,
+                TypeKind.CHAR,
+                TypeKind.FLOAT,
+                TypeKind.DOUBLE)
+        val ARRAY_CONVERTER = JavaFileObjects.forSourceLines("foo.bar.MyConverter",
+                """
+                package foo.bar;
+                import androidx.room.*;
+                public class MyConverter {
+                ${ALL_PRIMITIVES.joinToString("\n") {
+                    val arrayDef = "${it.name.toLowerCase()}[]"
+                    "@TypeConverter public static String" +
+                            " arrayIntoString($arrayDef input) { return null;}" +
+                            "@TypeConverter public static $arrayDef" +
+                            " stringIntoArray${it.name}(String input) { return null;}"
+                }}
+                ${ALL_PRIMITIVES.joinToString("\n") {
+                    val arrayDef = "${it.box()}[]"
+                    "@TypeConverter public static String" +
+                            " arrayIntoString($arrayDef input) { return null;}" +
+                            "@TypeConverter public static $arrayDef" +
+                            " stringIntoArray${it.name}Boxed(String input) { return null;}"
+                }}
+                }
+                """)
+
+        private fun TypeKind.box(): String {
+            return "java.lang." + when (this) {
+                TypeKind.INT -> "Integer"
+                TypeKind.CHAR -> "Character"
+                else -> this.name.toLowerCase().capitalize()
+            }
+        }
+
+        // these 2 box methods are ugly but makes tests nicer and they are private
+        private fun TypeKind.typeMirror(invocation: TestInvocation): TypeMirror {
+            return invocation.processingEnv.typeUtils.getPrimitiveType(this)
+        }
+
+        private fun TypeKind.affinity(): SQLTypeAffinity {
+            return when (this) {
+                TypeKind.FLOAT, TypeKind.DOUBLE -> SQLTypeAffinity.REAL
+                else -> SQLTypeAffinity.INTEGER
+            }
+        }
+
+        private fun TypeKind.box(invocation: TestInvocation): TypeMirror {
+            return invocation.processingEnv.elementUtils.getTypeElement(box()).asType()
+        }
+    }
+
+    @Test
+    fun primitives() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("${primitive.name.toLowerCase()} x;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "x",
+                                type = primitive.typeMirror(invocation),
+                                element = field.element,
+                                affinity = primitive.affinity()
+                        )))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun boxed() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("${primitive.box()} y;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "y",
+                                type = primitive.box(invocation),
+                                element = field.element,
+                                affinity = primitive.affinity())))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun columnName() {
+        singleEntity("""
+            @ColumnInfo(name = "foo")
+            @PrimaryKey
+            int x;
+            """) { field, invocation ->
+            assertThat(field, `is`(
+                    Field(name = "x",
+                            type = TypeKind.INT.typeMirror(invocation),
+                            element = field.element,
+                            columnName = "foo",
+                            affinity = SQLTypeAffinity.INTEGER)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun indexed() {
+        singleEntity("""
+            @ColumnInfo(name = "foo", index = true)
+            int x;
+            """) { field, invocation ->
+            assertThat(field, `is`(
+                    Field(name = "x",
+                            type = TypeKind.INT.typeMirror(invocation),
+                            element = field.element,
+                            columnName = "foo",
+                            affinity = SQLTypeAffinity.INTEGER,
+                            indexed = true)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun emptyColumnName() {
+        singleEntity("""
+            @ColumnInfo(name = "")
+            int x;
+            """) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
+    }
+
+    @Test
+    fun byteArrayWithEnforcedType() {
+        singleEntity("@TypeConverters(foo.bar.MyConverter.class)" +
+                "@ColumnInfo(typeAffinity = ColumnInfo.TEXT) byte[] arr;") { field, invocation ->
+            assertThat(field, `is`(Field(name = "arr",
+                    type = invocation.processingEnv.typeUtils.getArrayType(
+                            TypeKind.BYTE.typeMirror(invocation)),
+                    element = field.element,
+                    affinity = SQLTypeAffinity.TEXT)))
+            assertThat((field.cursorValueReader as? ColumnTypeAdapter)?.typeAffinity,
+                    `is`(SQLTypeAffinity.TEXT))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun primitiveArray() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
+                    "${primitive.toString().toLowerCase()}[] arr;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "arr",
+                                type = invocation.processingEnv.typeUtils.getArrayType(
+                                        primitive.typeMirror(invocation)),
+                                element = field.element,
+                                affinity = if (primitive == TypeKind.BYTE) {
+                                    SQLTypeAffinity.BLOB
+                                } else {
+                                    SQLTypeAffinity.TEXT
+                                })))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun boxedArray() {
+        ALL_PRIMITIVES.forEach { primitive ->
+            singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
+                    "${primitive.box()}[] arr;") { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "arr",
+                                type = invocation.processingEnv.typeUtils.getArrayType(
+                                        primitive.box(invocation)),
+                                element = field.element,
+                                affinity = SQLTypeAffinity.TEXT)))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun generic() {
+        singleEntity("""
+                static class BaseClass<T> {
+                    T item;
+                }
+                @Entity
+                static class Extending extends BaseClass<java.lang.Integer> {
+                }
+                """) { field, invocation ->
+            assertThat(field, `is`(Field(name = "item",
+                    type = TypeKind.INT.box(invocation),
+                    element = field.element,
+                    affinity = SQLTypeAffinity.INTEGER)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun unboundGeneric() {
+        singleEntity("""
+                @Entity
+                static class BaseClass<T> {
+                    T item;
+                }
+                """) { _, _ -> }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
+    }
+
+    @Test
+    fun nameVariations() {
+        simpleRun {
+            assertThat(Field(mock(Element::class.java), "x", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
+            assertThat(Field(mock(Element::class.java), "x", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("x")))
+            assertThat(Field(mock(Element::class.java), "xAll",
+                    TypeKind.BOOLEAN.typeMirror(it), SQLTypeAffinity.INTEGER)
+                    .nameWithVariations, `is`(arrayListOf("xAll")))
+        }
+    }
+
+    @Test
+    fun nameVariations_is() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "isX", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX", "x")))
+            assertThat(Field(elm, "isX", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("isX")))
+            assertThat(Field(elm, "is", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("is")))
+            assertThat(Field(elm, "isAllItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("isAllItems", "allItems")))
+        }
+    }
+
+    @Test
+    fun nameVariations_has() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "hasX", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX", "x")))
+            assertThat(Field(elm, "hasX", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("hasX")))
+            assertThat(Field(elm, "has", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("has")))
+            assertThat(Field(elm, "hasAllItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("hasAllItems", "allItems")))
+        }
+    }
+
+    @Test
+    fun nameVariations_m() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "mall", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mall")))
+            assertThat(Field(elm, "mallVars", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mallVars")))
+            assertThat(Field(elm, "mAll", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("mAll", "all")))
+            assertThat(Field(elm, "m", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("m")))
+            assertThat(Field(elm, "mallItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("mallItems")))
+            assertThat(Field(elm, "mAllItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("mAllItems", "allItems")))
+        }
+    }
+
+    @Test
+    fun nameVariations_underscore() {
+        val elm = mock(Element::class.java)
+        simpleRun {
+            assertThat(Field(elm, "_all", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_all", "all")))
+            assertThat(Field(elm, "_", TypeKind.INT.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations, `is`(arrayListOf("_")))
+            assertThat(Field(elm, "_allItems", TypeKind.BOOLEAN.typeMirror(it),
+                    SQLTypeAffinity.INTEGER).nameWithVariations,
+                    `is`(arrayListOf("_allItems", "allItems")))
+        }
+    }
+
+    @Test
+    fun collate() {
+        Collate.values().forEach { collate ->
+            singleEntity("""
+            @PrimaryKey
+            @ColumnInfo(collate = ColumnInfo.${collate.name})
+            String code;
+            """) { field, invocation ->
+                assertThat(field, `is`(
+                        Field(name = "code",
+                                type = invocation.context.COMMON_TYPES.STRING,
+                                element = field.element,
+                                columnName = "code",
+                                collate = collate,
+                                affinity = SQLTypeAffinity.TEXT)))
+            }.compilesWithoutError()
+        }
+    }
+
+    fun singleEntity(vararg input: String, handler: (Field, invocation: TestInvocation) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
+                        ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
+                ), ARRAY_CONVERTER))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(androidx.room.Entity::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, field) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Entity::class.java)
+                                    .map {
+                                        Pair(it, invocation.processingEnv.elementUtils
+                                                .getAllMembers(MoreElements.asType(it))
+                                                .firstOrNull { it.kind == ElementKind.FIELD })
+                                    }
+                                    .first { it.second != null }
+                            val entityContext =
+                                    EntityProcessor(invocation.context, MoreElements.asType(owner))
+                                            .context
+                            val parser = FieldProcessor(
+                                    baseContext = entityContext,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    element = field!!,
+                                    bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+                                    fieldParent = null)
+                            handler(parser.process(), invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
new file mode 100644
index 0000000..c533ed0
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import COMMON
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.typeName
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.InsertionMethod
+import androidx.room.vo.InsertionMethod.Type
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth.assertAbout
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class InsertionMethodProcessorTest {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import androidx.room.*;
+                import java.util.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
+        val BOOK_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Book")
+    }
+
+    @Test
+    fun readNoParams() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo();
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("foo"))
+            assertThat(insertion.parameters.size, `is`(0))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+            assertThat(insertion.entities.size, `is`(0))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT)
+    }
+
+    @Test
+    fun insertSingle() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long foo(User user);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("foo"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities["user"]?.typeName,
+                    `is`(ClassName.get("foo.bar", "User") as TypeName))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.LONG))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertNotAnEntity() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(NotAnEntity notValid);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("foo"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.entityType, `is`(nullValue()))
+            assertThat(insertion.entities.size, `is`(0))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
+        )
+    }
+
+    @Test
+    fun insertTwo() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(User u1, User u2);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("foo"))
+
+            assertThat(insertion.parameters.size, `is`(2))
+            insertion.parameters.forEach {
+                assertThat(it.type.typeName(), `is`(USER_TYPE_NAME))
+                assertThat(it.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            }
+            assertThat(insertion.entities.size, `is`(2))
+            assertThat(insertion.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities["u2"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "u2")))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertList() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public List<Long> insertUsers(List<User> users);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(
+                            ClassName.get("java.util", "List"), USER_TYPE_NAME) as TypeName))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "List"),
+                            ClassName.get("java.lang", "Long")) as TypeName
+            ))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertArray() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void insertUsers(User[] users);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertSet() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void insertUsers(Set<User> users);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "Set")
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertQueue() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void insertUsers(Queue<User> users);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("insertUsers"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "Queue")
+                            , USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertIterable() {
+        singleInsertMethod("""
+                @Insert
+                abstract public void insert(Iterable<User> users);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("insert"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(ParameterizedTypeName.get(
+                    ClassName.get("java.lang", "Iterable"), USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertCustomCollection() {
+        singleInsertMethod("""
+                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
+                @Insert
+                abstract public void insert(MyList<String, User> users);
+                """) { insertion, _ ->
+            assertThat(insertion.name, `is`("insert"))
+            assertThat(insertion.parameters.size, `is`(1))
+            val param = insertion.parameters.first()
+            assertThat(param.type.typeName(), `is`(ParameterizedTypeName.get(
+                    ClassName.get("foo.bar", "MyClass.MyList"),
+                    CommonTypeNames.STRING, USER_TYPE_NAME) as TypeName))
+            assertThat(insertion.entities.size, `is`(1))
+            assertThat(insertion.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun insertDifferentTypes() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(User u1, Book b1);
+                """) { insertion, _ ->
+            assertThat(insertion.parameters.size, `is`(2))
+            assertThat(insertion.parameters[0].type.typeName().toString(),
+                    `is`("foo.bar.User"))
+            assertThat(insertion.parameters[1].type.typeName().toString(),
+                    `is`("foo.bar.Book"))
+            assertThat(insertion.parameters.map { it.name }, `is`(listOf("u1", "b1")))
+            assertThat(insertion.returnType.typeName(), `is`(TypeName.VOID))
+            assertThat(insertion.entities.size, `is`(2))
+            assertThat(insertion.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(insertion.entities["b1"]?.typeName, `is`(BOOK_TYPE_NAME))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun onConflict_Default() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public void foo(User user);
+                """) { insertion, _ ->
+            assertThat(insertion.onConflict, `is`(OnConflictStrategy.ABORT))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun onConflict_Invalid() {
+        singleInsertMethod(
+                """
+                @Insert(onConflict = -1)
+                abstract public void foo(User user);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+    }
+
+    @Test
+    fun onConflict_EachValue() {
+        listOf(
+                Pair("REPLACE", 1),
+                Pair("ROLLBACK", 2),
+                Pair("ABORT", 3),
+                Pair("FAIL", 4),
+                Pair("IGNORE", 5)
+        ).forEach { pair ->
+            singleInsertMethod(
+                    """
+                @Insert(onConflict=OnConflictStrategy.${pair.first})
+                abstract public void foo(User user);
+                """) { insertion, _ ->
+                assertThat(insertion.onConflict, `is`(pair.second))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun invalidReturnType() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public int foo(User user);
+                """) { insertion, _ ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE)
+    }
+
+    @Test
+    fun mismatchedReturnType() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long[] foo(User user);
+                """) { insertion, _ ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.insertionMethodReturnTypeMismatch(
+                        ArrayTypeName.of(TypeName.LONG),
+                        InsertionMethodProcessor.SINGLE_ITEM_SET.map { it.returnTypeName }))
+    }
+
+    @Test
+    fun mismatchedReturnType2() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long foo(User... user);
+                """) { insertion, _ ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.insertionMethodReturnTypeMismatch(
+                        TypeName.LONG,
+                        InsertionMethodProcessor.MULTIPLE_ITEM_SET.map { it.returnTypeName }))
+    }
+
+    @Test
+    fun mismatchedReturnType3() {
+        singleInsertMethod(
+                """
+                @Insert
+                abstract public long foo(User user1, User user2);
+                """) { insertion, _ ->
+            assertThat(insertion.insertionType, `is`(nullValue()))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.insertionMethodReturnTypeMismatch(
+                        TypeName.LONG,
+                        InsertionMethodProcessor.VOID_SET.map { it.returnTypeName }))
+    }
+
+    @Test
+    fun validReturnTypes() {
+        listOf(
+                Pair("void", Type.INSERT_VOID),
+                Pair("long", Type.INSERT_SINGLE_ID),
+                Pair("long[]", Type.INSERT_ID_ARRAY),
+                Pair("Long[]", Type.INSERT_ID_ARRAY_BOX),
+                Pair("List<Long>", Type.INSERT_ID_LIST)
+        ).forEach { pair ->
+            val dots = if (pair.second in setOf(Type.INSERT_ID_LIST, Type.INSERT_ID_ARRAY,
+                    Type.INSERT_ID_ARRAY_BOX)) {
+                "..."
+            } else {
+                ""
+            }
+            singleInsertMethod(
+                    """
+                @Insert
+                abstract public ${pair.first} foo(User$dots user);
+                """) { insertion, _ ->
+                assertThat(insertion.insertMethodTypeFor(insertion.parameters.first()),
+                        `is`(pair.second))
+                assertThat(pair.toString(), insertion.insertionType, `is`(pair.second))
+            }.compilesWithoutError()
+        }
+    }
+
+    fun singleInsertMethod(
+            vararg input: String,
+            handler: (InsertionMethod, TestInvocation) -> Unit
+    ): CompileTester {
+        return assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ), COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Insert::class, Dao::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            MoreElements.isAnnotationPresent(it,
+                                                                    Insert::class.java)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val processor = InsertionMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            val processed = processor.process()
+                            handler(processed, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
new file mode 100644
index 0000000..3506973
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -0,0 +1,958 @@
+/*
+ * 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 androidx.room.processor
+
+import COMMON
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
+import androidx.room.processor.ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY
+import androidx.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+import androidx.room.processor.ProcessorErrors.RELATION_NOT_COLLECTION
+import androidx.room.processor.ProcessorErrors.relationCannotFindEntityField
+import androidx.room.processor.ProcessorErrors.relationCannotFindParentEntityField
+import androidx.room.testing.TestInvocation
+import androidx.room.vo.CallType
+import androidx.room.vo.Constructor
+import androidx.room.vo.EmbeddedField
+import androidx.room.vo.Field
+import androidx.room.vo.Pojo
+import androidx.room.vo.RelationCollector
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.CoreMatchers.sameInstance
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import simpleRun
+import javax.lang.model.element.Element
+import javax.tools.JavaFileObject
+
+/**
+ * Some of the functionality is tested via EntityProcessor.
+ */
+@RunWith(JUnit4::class)
+class PojoProcessorTest {
+
+    companion object {
+        val MY_POJO: ClassName = ClassName.get("foo.bar", "MyPojo")
+        val HEADER = """
+            package foo.bar;
+            import androidx.room.*;
+            import java.util.*;
+            public class MyPojo {
+            """
+        val FOOTER = "\n}"
+    }
+
+    private fun String.toJFO(qName: String) = JavaFileObjects.forSourceLines(qName, this)
+
+    @Test
+    fun inheritedPrivate() {
+        val parent = """
+            package foo.bar.x;
+            import androidx.room.*;
+            public class BaseClass {
+                private String baseField;
+                public String getBaseField(){ return baseField; }
+                public void setBaseField(String baseField){ }
+            }
+        """
+        simpleRun(
+                """
+                package foo.bar;
+                import androidx.room.*;
+                public class ${MY_POJO.simpleName()} extends foo.bar.x.BaseClass {
+                    public String myField;
+                }
+                """.toJFO(MY_POJO.toString()),
+                parent.toJFO("foo.bar.x.BaseClass")) { invocation ->
+            val pojo = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                    parent = null).process()
+            assertThat(pojo.fields.find { it.name == "myField" }, notNullValue())
+            assertThat(pojo.fields.find { it.name == "baseField" }, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun transient_ignore() {
+        singleRun("""
+            transient int foo;
+            int bar;
+        """) { pojo ->
+            assertThat(pojo.fields.size, `is`(1))
+            assertThat(pojo.fields[0].name, `is`("bar"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun transient_withColumnInfo() {
+        singleRun("""
+            @ColumnInfo
+            transient int foo;
+            int bar;
+        """) { pojo ->
+            assertThat(pojo.fields.map { it.name }.toSet(), `is`(setOf("bar", "foo")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun transient_embedded() {
+        singleRun("""
+            @Embedded
+            transient Foo foo;
+            int bar;
+            static class Foo {
+                int x;
+            }
+        """) { pojo ->
+            assertThat(pojo.fields.map { it.name }.toSet(), `is`(setOf("x", "bar")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun transient_insideEmbedded() {
+        singleRun("""
+            @Embedded
+            Foo foo;
+            int bar;
+            static class Foo {
+                transient int x;
+                int y;
+            }
+        """) { pojo ->
+            assertThat(pojo.fields.map { it.name }.toSet(), `is`(setOf("bar", "y")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun transient_relation() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public transient List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+            assertThat(pojo.relations.size, `is`(1))
+            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
+            assertThat(pojo.relations.first().parentField.name, `is`("id"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun embedded() {
+        singleRun(
+                """
+                int id;
+                @Embedded
+                Point myPoint;
+                static class Point {
+                    int x;
+                    int y;
+                }
+                """
+        ) { pojo ->
+            assertThat(pojo.fields.size, `is`(3))
+            assertThat(pojo.fields[1].name, `is`("x"))
+            assertThat(pojo.fields[2].name, `is`("y"))
+            assertThat(pojo.fields[0].parent, nullValue())
+            assertThat(pojo.fields[1].parent, notNullValue())
+            assertThat(pojo.fields[2].parent, notNullValue())
+            val parent = pojo.fields[2].parent!!
+            assertThat(parent.prefix, `is`(""))
+            assertThat(parent.field.name, `is`("myPoint"))
+            assertThat(parent.pojo.typeName,
+                    `is`(ClassName.get("foo.bar.MyPojo", "Point") as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun embeddedWithPrefix() {
+        singleRun(
+                """
+                int id;
+                @Embedded(prefix = "foo")
+                Point myPoint;
+                static class Point {
+                    int x;
+                    @ColumnInfo(name = "y2")
+                    int y;
+                }
+                """
+        ) { pojo ->
+            assertThat(pojo.fields.size, `is`(3))
+            assertThat(pojo.fields[1].name, `is`("x"))
+            assertThat(pojo.fields[2].name, `is`("y"))
+            assertThat(pojo.fields[1].columnName, `is`("foox"))
+            assertThat(pojo.fields[2].columnName, `is`("fooy2"))
+            val parent = pojo.fields[2].parent!!
+            assertThat(parent.prefix, `is`("foo"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun nestedEmbedded() {
+        singleRun(
+                """
+                int id;
+                @Embedded(prefix = "foo")
+                Point myPoint;
+                static class Point {
+                    int x;
+                    @ColumnInfo(name = "y2")
+                    int y;
+                    @Embedded(prefix = "bar")
+                    Coordinate coordinate;
+                }
+                static class Coordinate {
+                    double lat;
+                    double lng;
+                    @Ignore
+                    String ignored;
+                }
+                """
+        ) { pojo ->
+            assertThat(pojo.fields.size, `is`(5))
+            assertThat(pojo.fields.map { it.columnName }, `is`(
+                    listOf("id", "foox", "fooy2", "foobarlat", "foobarlng")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun duplicateColumnNames() {
+        singleRun(
+                """
+                int id;
+                @ColumnInfo(name = "id")
+                int another;
+                """
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.pojoDuplicateFieldNames("id", listOf("id", "another"))
+        ).and().withErrorContaining(
+                POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+        ).and().withErrorCount(3)
+    }
+
+    @Test
+    fun duplicateColumnNamesFromEmbedded() {
+        singleRun(
+                """
+                int id;
+                @Embedded
+                Foo foo;
+                static class Foo {
+                    @ColumnInfo(name = "id")
+                    int x;
+                }
+                """
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.pojoDuplicateFieldNames("id", listOf("id", "foo > x"))
+        ).and().withErrorContaining(
+                POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
+        ).and().withErrorCount(3)
+    }
+
+    @Test
+    fun dropSubPrimaryKeyNoWarningForPojo() {
+        singleRun(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                Point myPoint;
+                static class Point {
+                    @PrimaryKey
+                    int x;
+                    int y;
+                }
+                """
+        ) { _ ->
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun relation_notCollection() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public User user;
+                """, COMMON.USER
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(RELATION_NOT_COLLECTION)
+    }
+
+    @Test
+    fun relation_columnInfo() {
+        singleRun(
+                """
+                int id;
+                @ColumnInfo
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION)
+    }
+
+    @Test
+    fun relation_notEntity() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<NotAnEntity> user;
+                """, COMMON.NOT_AN_ENTITY
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
+    }
+
+    @Test
+    fun relation_missingParent() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "idk", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(
+                relationCannotFindParentEntityField("foo.bar.MyPojo", "idk", listOf("id"))
+        )
+    }
+
+    @Test
+    fun relation_missingEntityField() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "idk")
+                public List<User> user;
+                """, COMMON.USER
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(
+                relationCannotFindEntityField("foo.bar.User", "idk",
+                        listOf("uid", "name", "lastName", "age"))
+        )
+    }
+
+    @Test
+    fun relation_missingType() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(CANNOT_FIND_TYPE)
+    }
+
+    @Test
+    fun relation_nestedField() {
+        singleRun(
+                """
+                static class Nested {
+                    @ColumnInfo(name = "foo")
+                    public int id;
+                }
+                @Embedded
+                Nested nested;
+                @Relation(parentColumn = "foo", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+            assertThat(pojo.relations.first().parentField.columnName, `is`("foo"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun relation_nestedRelation() {
+        singleRun(
+                """
+                static class UserWithNested {
+                    @Embedded
+                    public User user;
+                    @Relation(parentColumn = "uid", entityColumn = "uid")
+                    public List<User> selfs;
+                }
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid", entity = User.class)
+                public List<UserWithNested> user;
+                """, COMMON.USER
+        ) { pojo, _ ->
+            assertThat(pojo.relations.first().parentField.name, `is`("id"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun relation_affinityMismatch() {
+        singleRun(
+                """
+                String id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo, invocation ->
+            // trigger assignment evaluation
+            RelationCollector.createCollectors(invocation.context, pojo.relations)
+            assertThat(pojo.relations.size, `is`(1))
+            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
+            assertThat(pojo.relations.first().parentField.name, `is`("id"))
+        }.compilesWithoutError().withWarningContaining(
+                ProcessorErrors.relationAffinityMismatch(
+                        parentAffinity = SQLTypeAffinity.TEXT,
+                        childAffinity = SQLTypeAffinity.INTEGER,
+                        parentColumn = "id",
+                        childColumn = "uid")
+        )
+    }
+
+    @Test
+    fun relation_simple() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                public List<User> user;
+                """, COMMON.USER
+        ) { pojo ->
+            assertThat(pojo.relations.size, `is`(1))
+            assertThat(pojo.relations.first().entityField.name, `is`("uid"))
+            assertThat(pojo.relations.first().parentField.name, `is`("id"))
+        }.compilesWithoutError().withWarningCount(0)
+    }
+
+    @Test
+    fun relation_badProjection() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid", projection={"i_dont_exist"})
+                public List<User> user;
+                """, COMMON.USER
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.relationBadProject("foo.bar.User", listOf("i_dont_exist"),
+                        listOf("uid", "name", "lastName", "ageColumn"))
+        )
+    }
+
+    @Test
+    fun relation_badReturnTypeInGetter() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid")
+                private List<User> user;
+                public void setUser(List<User> user){ this.user = user;}
+                public User getUser(){return null;}
+                """, COMMON.USER
+        ) { _ ->
+        }.failsToCompile().withErrorContaining(CANNOT_FIND_GETTER_FOR_FIELD)
+    }
+
+    @Test
+    fun relation_primitiveList() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid",  projection={"uid"},
+                        entity = User.class)
+                public List<Integer> userIds;
+                """, COMMON.USER
+        ) { pojo ->
+            assertThat(pojo.relations.size, `is`(1))
+            val rel = pojo.relations.first()
+            assertThat(rel.projection, `is`(listOf("uid")))
+            assertThat(rel.entity.typeName, `is`(COMMON.USER_TYPE_NAME as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun relation_stringList() {
+        singleRun(
+                """
+                int id;
+                @Relation(parentColumn = "id", entityColumn = "uid",  projection={"name"},
+                        entity = User.class)
+                public List<String> userNames;
+                """, COMMON.USER
+        ) { pojo ->
+            assertThat(pojo.relations.size, `is`(1))
+            val rel = pojo.relations.first()
+            assertThat(rel.projection, `is`(listOf("name")))
+            assertThat(rel.entity.typeName, `is`(COMMON.USER_TYPE_NAME as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun cache() {
+        val pojo = """
+            $HEADER
+            int id;
+            $FOOTER
+            """.toJFO(MY_POJO.toString())
+        simpleRun(pojo) { invocation ->
+            val element = invocation.typeElement(MY_POJO.toString())
+            val pojo1 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.BIND_TO_STMT, null).process()
+            assertThat(pojo1, notNullValue())
+            val pojo2 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.BIND_TO_STMT, null).process()
+            assertThat(pojo2, sameInstance(pojo1))
+
+            val pojo3 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.READ_FROM_CURSOR, null).process()
+            assertThat(pojo3, notNullValue())
+            assertThat(pojo3, not(sameInstance(pojo1)))
+
+            val pojo4 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, null).process()
+            assertThat(pojo4, notNullValue())
+            assertThat(pojo4, not(sameInstance(pojo1)))
+            assertThat(pojo4, not(sameInstance(pojo3)))
+
+            val pojo5 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, null).process()
+            assertThat(pojo5, sameInstance(pojo4))
+
+            val type = invocation.context.COMMON_TYPES.STRING
+            val mockElement = mock(Element::class.java)
+            doReturn(type).`when`(mockElement).asType()
+            val fakeField = Field(
+                    element = mockElement,
+                    name = "foo",
+                    type = type,
+                    affinity = SQLTypeAffinity.TEXT,
+                    columnName = "foo",
+                    parent = null,
+                    indexed = false
+            )
+            val fakeEmbedded = EmbeddedField(fakeField, "", null)
+
+            val pojo6 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, fakeEmbedded).process()
+            assertThat(pojo6, notNullValue())
+            assertThat(pojo6, not(sameInstance(pojo1)))
+            assertThat(pojo6, not(sameInstance(pojo3)))
+            assertThat(pojo6, not(sameInstance(pojo4)))
+
+            val pojo7 = PojoProcessor(invocation.context, element,
+                    FieldProcessor.BindingScope.TWO_WAY, fakeEmbedded).process()
+            assertThat(pojo7, sameInstance(pojo6))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_empty() {
+        val pojoCode = """
+            public String mName;
+            """
+        singleRun(pojoCode) { pojo ->
+            assertThat(pojo.constructor, notNullValue())
+            assertThat(pojo.constructor?.params, `is`(emptyList<Constructor.Param>()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_twoFieldsExactMatch() {
+        val pojoCode = """
+            public String mName;
+            public String _name;
+            public MyPojo(String mName) {
+            }
+            """
+        singleRun(pojoCode) { pojo ->
+            val param = pojo.constructor?.params?.first()
+            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
+            assertThat((param as Constructor.FieldParam).field.name, `is`("mName"))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_oneTypeMatches() {
+        val pojoCode = """
+            public String mName;
+            public int _name;
+            public MyPojo(String name) {
+            }
+            """
+        singleRun(pojoCode) { pojo ->
+            val param = pojo.constructor?.params?.first()
+            assertThat(param, instanceOf(Constructor.FieldParam::class.java))
+            assertThat((param as Constructor.FieldParam).field.name, `is`("mName"))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_ambiguous_twoFields() {
+        val pojo = """
+            String mName;
+            String _name;
+            public MyPojo(String name) {
+            }
+            """
+        singleRun(pojo) { _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.ambigiousConstructor(MY_POJO.toString(),
+                        "name", listOf("mName", "_name"))
+        )
+    }
+
+    @Test
+    fun constructor_noMatchBadType() {
+        singleRun("""
+            int foo;
+            public MyPojo(String foo) {
+            }
+        """) { _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_noMatch() {
+        singleRun("""
+            String mName;
+            String _name;
+            public MyPojo(String foo) {
+            }
+        """) { _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_noMatchMultiArg() {
+        singleRun("""
+            String mName;
+            int bar;
+            public MyPojo(String foo, String name) {
+            }
+        """) { _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
+    }
+
+    @Test
+    fun constructor_multipleMatching() {
+        singleRun("""
+            String mName;
+            String mLastName;
+            public MyPojo(String name) {
+            }
+            public MyPojo(String name, String lastName) {
+            }
+        """) { _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
+    }
+
+    @Test
+    fun constructor_multipleMatchingWithIgnored() {
+        singleRun("""
+            String mName;
+            String mLastName;
+            @Ignore
+            public MyPojo(String name) {
+            }
+            public MyPojo(String name, String lastName) {
+            }
+        """) { pojo ->
+            assertThat(pojo.constructor, notNullValue())
+            assertThat(pojo.constructor?.params?.size, `is`(2))
+            assertThat(pojo.fields.find { it.name == "mName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+            assertThat(pojo.fields.find { it.name == "mLastName" }?.setter?.callType,
+                    `is`(CallType.CONSTRUCTOR))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_dontTryForBindToScope() {
+        singleRun("""
+            String mName;
+            String mLastName;
+        """) { _, invocation ->
+            val process2 = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
+                    parent = null).process()
+            assertThat(process2.constructor, nullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_bindForTwoWay() {
+        singleRun("""
+            String mName;
+            String mLastName;
+        """) { _, invocation ->
+            val process2 = PojoProcessor(baseContext = invocation.context,
+                    element = invocation.typeElement(MY_POJO.toString()),
+                    bindingScope = FieldProcessor.BindingScope.TWO_WAY,
+                    parent = null).process()
+            assertThat(process2.constructor, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun constructor_multipleMatching_withNoArg() {
+        singleRun("""
+            String mName;
+            String mLastName;
+            public MyPojo() {
+            }
+            public MyPojo(String name, String lastName) {
+            }
+        """) { pojo ->
+            assertThat(pojo.constructor?.params?.size ?: -1, `is`(0))
+        }.compilesWithoutError().withWarningContaining(
+                ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS_CHOOSING_NO_ARG)
+    }
+
+    @Test // added for b/69562125
+    fun constructor_withNullabilityAnnotation() {
+        singleRun("""
+            String mName;
+            public MyPojo(@androidx.annotation.NonNull String name) {}
+            """) { pojo ->
+            val constructor = pojo.constructor
+            assertThat(constructor, notNullValue())
+            assertThat(constructor!!.params.size, `is`(1))
+        }.compilesWithoutError()
+    }
+
+    @Test // b/69118713 common mistake so we better provide a good explanation
+    fun constructor_relationParameter() {
+        singleRun("""
+            @Relation(entity = foo.bar.User.class, parentColumn = "uid", entityColumn="uid",
+            projection = "name")
+            public List<String> items;
+            public String uid;
+            public MyPojo(String uid, List<String> items) {
+            }
+            """, COMMON.USER) { _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RELATION_CANNOT_BE_CONSTRUCTOR_PARAMETER
+        )
+    }
+
+    @Test
+    fun recursion_1Level() {
+        singleRun(
+                """
+                @Embedded
+                MyPojo myPojo;
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo -> foo.bar.MyPojo"))
+    }
+
+    @Test
+    fun recursion_2Levels_relationToEmbed() {
+        singleRun(
+                """
+                int pojoId;
+
+                @Relation(parentColumn = "pojoId", entityColumn = "entityId")
+                List<MyEntity> myEntity;
+
+                @Entity
+                static class MyEntity {
+                    int entityId;
+
+                    @Embedded
+                    MyPojo myPojo;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo -> foo.bar.MyPojo.MyEntity -> foo.bar.MyPojo"))
+    }
+
+    @Test
+    fun recursion_2Levels_onlyEmbeds_pojoToEntity() {
+        singleRun(
+                """
+                @Embedded
+                A a;
+
+                @Entity
+                static class A {
+                    @Embedded
+                    MyPojo myPojo;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
+    }
+
+    @Test
+    fun recursion_2Levels_onlyEmbeds_onlyPojos() {
+        singleRun(
+                """
+                @Embedded
+                A a;
+                static class A {
+                    @Embedded
+                    MyPojo myPojo;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
+    }
+
+    @Test
+    fun recursion_3Levels() {
+        singleRun(
+                """
+                @Embedded
+                A a;
+                public static class A {
+                    @Embedded
+                    B b;
+                }
+                public static class B {
+                    @Embedded
+                    MyPojo myPojo;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo"))
+    }
+
+    @Test
+    fun recursion_1Level_1LevelDown() {
+        singleRun(
+                """
+                @Embedded
+                A a;
+                static class A {
+                    @Embedded
+                    B b;
+                }
+                static class B {
+                    @Embedded
+                    A a;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo.A -> foo.bar.MyPojo.B -> foo.bar.MyPojo.A"))
+    }
+
+    @Test
+    fun recursion_branchAtLevel0_afterBackTrack() {
+        singleRun(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                A a;
+                @Embedded
+                C c;
+                static class A {
+                    @Embedded
+                    B b;
+                }
+                static class B {
+                }
+                static class C {
+                    @Embedded
+                    MyPojo myPojo;
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo -> foo.bar.MyPojo.C -> foo.bar.MyPojo"))
+    }
+
+    @Test
+    fun recursion_branchAtLevel1_afterBackTrack() {
+        singleRun(
+                """
+                @PrimaryKey
+                int id;
+                @Embedded
+                A a;
+                static class A {
+                    @Embedded
+                    B b;
+                    @Embedded
+                    MyPojo myPojo;
+                }
+                static class B {
+                    @Embedded
+                    C c;
+                }
+                static class C {
+                }
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
+                        "foo.bar.MyPojo -> foo.bar.MyPojo.A -> foo.bar.MyPojo"))
+    }
+
+    fun singleRun(
+            code: String, vararg jfos: JavaFileObject, handler: (Pojo) -> Unit): CompileTester {
+        return singleRun(code, *jfos) { pojo, _ ->
+            handler(pojo)
+        }
+    }
+
+    fun singleRun(
+            code: String, vararg jfos: JavaFileObject,
+            handler: (Pojo, TestInvocation) -> Unit): CompileTester {
+        val pojoJFO = """
+                $HEADER
+                $code
+                $FOOTER
+                """.toJFO(MY_POJO.toString())
+        val all = (jfos.toList() + pojoJFO).toTypedArray()
+        return simpleRun(*all) { invocation ->
+            handler.invoke(
+                    PojoProcessor(baseContext = invocation.context,
+                            element = invocation.typeElement(MY_POJO.toString()),
+                            bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
+                            parent = null).process(),
+                    invocation
+            )
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
new file mode 100644
index 0000000..9d25c53
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -0,0 +1,817 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.processor
+
+import COMMON
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.LifecyclesTypeNames
+import androidx.room.ext.PagingTypeNames
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.typeName
+import androidx.room.parser.Table
+import androidx.room.processor.ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER
+import androidx.room.solver.query.result.DataSourceFactoryQueryResultBinder
+import androidx.room.solver.query.result.LiveDataQueryResultBinder
+import androidx.room.solver.query.result.PojoRowAdapter
+import androidx.room.solver.query.result.SingleEntityQueryResultAdapter
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.Field
+import androidx.room.vo.QueryMethod
+import androidx.room.vo.Warning
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth.assertAbout
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
+import createVerifierFromEntities
+import mockElementAndType
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.hasItem
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeMirror
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(Parameterized::class)
+class QueryMethodProcessorTest(val enableVerification: Boolean) {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val POJO: ClassName = ClassName.get("foo.bar", "MyClass.Pojo")
+        @Parameterized.Parameters(name = "enableDbVerification={0}")
+        @JvmStatic
+        fun getParams() = arrayOf(true, false)
+
+        fun createField(name: String, columnName: String? = null): Field {
+            val (element, type) = mockElementAndType()
+            return Field(
+                    element = element,
+                    name = name,
+                    type = type,
+                    columnName = columnName ?: name,
+                    affinity = null
+            )
+        }
+    }
+
+    @Test
+    fun testReadNoParams() {
+        singleQueryMethod(
+                """
+                @Query("SELECT * from User")
+                abstract public int[] foo();
+                """) { parsedQuery, _ ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(0))
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testSingleParam() {
+        singleQueryMethod(
+                """
+                @Query("SELECT * from User where uid = :x")
+                abstract public long foo(int x);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.LONG))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            val param = parsedQuery.parameters.first()
+            assertThat(param.name, `is`("x"))
+            assertThat(param.sqlName, `is`("x"))
+            assertThat(param.type,
+                    `is`(invocation.processingEnv.typeUtils.getPrimitiveType(INT) as TypeMirror))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVarArgs() {
+        singleQueryMethod(
+                """
+                @Query("SELECT * from User where uid in (:ids)")
+                abstract public long foo(int... ids);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.LONG))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            val param = parsedQuery.parameters.first()
+            assertThat(param.name, `is`("ids"))
+            assertThat(param.sqlName, `is`("ids"))
+            val types = invocation.processingEnv.typeUtils
+            assertThat(param.type,
+                    `is`(types.getArrayType(types.getPrimitiveType(INT)) as TypeMirror))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testParamBindingMatchingNoName() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, _ ->
+            val section = parsedQuery.query.bindSections.first()
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping, `is`(listOf(Pair(section, param))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testParamBindingMatchingSimpleBind() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, _ ->
+            val section = parsedQuery.query.bindSections.first()
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(section, param))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testParamBindingTwoBindVarsIntoTheSameParameter() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id OR uid = :id")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, _ ->
+            val section = parsedQuery.query.bindSections[0]
+            val section2 = parsedQuery.query.bindSections[1]
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(section2, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(section, param), Pair(section2, param))))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMissingParameterForBinding() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where uid = :id OR uid = :uid")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, _ ->
+            val section = parsedQuery.query.bindSections[0]
+            val section2 = parsedQuery.query.bindSections[1]
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(section, notNullValue())
+            assertThat(section2, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(section, param), Pair(section2, null))))
+        }
+                .failsToCompile()
+                .withErrorContaining(
+                        ProcessorErrors.missingParameterForBindVariable(listOf(":uid")))
+    }
+
+    @Test
+    fun test2MissingParameterForBinding() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where name = :bar AND uid = :id OR uid = :uid")
+                abstract public long getIdById(int id);
+                """) { parsedQuery, _ ->
+            val bar = parsedQuery.query.bindSections[0]
+            val id = parsedQuery.query.bindSections[1]
+            val uid = parsedQuery.query.bindSections[2]
+            val param = parsedQuery.parameters.firstOrNull()
+            assertThat(bar, notNullValue())
+            assertThat(id, notNullValue())
+            assertThat(uid, notNullValue())
+            assertThat(param, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(bar, null), Pair(id, param), Pair(uid, null))))
+        }
+                .failsToCompile()
+                .withErrorContaining(
+                        ProcessorErrors.missingParameterForBindVariable(listOf(":bar", ":uid")))
+    }
+
+    @Test
+    fun testUnusedParameters() {
+        singleQueryMethod(
+                """
+                @Query("SELECT uid from User where name = :bar")
+                abstract public long getIdById(int bar, int whyNotUseMe);
+                """) { parsedQuery, _ ->
+            val bar = parsedQuery.query.bindSections[0]
+            val barParam = parsedQuery.parameters.firstOrNull()
+            assertThat(bar, notNullValue())
+            assertThat(barParam, notNullValue())
+            assertThat(parsedQuery.sectionToParamMapping,
+                    `is`(listOf(Pair(bar, barParam))))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.unusedQueryMethodParameter(listOf("whyNotUseMe")))
+    }
+
+    @Test
+    fun testNameWithUnderscore() {
+        singleQueryMethod(
+                """
+                @Query("select * from User where uid = :_blah")
+                abstract public long getSth(int _blah);
+                """
+        ) { _, _ -> }
+                .failsToCompile()
+                .withErrorContaining(ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE)
+    }
+
+    @Test
+    fun testGenericReturnType() {
+        singleQueryMethod(
+                """
+                @Query("select * from User")
+                abstract public <T> ${CommonTypeNames.LIST}<T> foo(int x);
+                """) { parsedQuery, _ ->
+            val expected: TypeName = ParameterizedTypeName.get(ClassName.get(List::class.java),
+                    TypeVariableName.get("T"))
+            assertThat(parsedQuery.returnType.typeName(), `is`(expected))
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
+    }
+
+    @Test
+    fun testBadQuery() {
+        singleQueryMethod(
+                """
+                @Query("select * from :1 :2")
+                abstract public long foo(int x);
+                """) { _, _ ->
+            // do nothing
+        }.failsToCompile()
+                .withErrorContaining("UNEXPECTED_CHAR=:")
+    }
+
+    @Test
+    fun testLiveDataWithWithClause() {
+        singleQueryMethod(
+                """
+                @Query("WITH RECURSIVE tempTable(n, fact) AS (SELECT 0, 1 UNION ALL SELECT n+1,"
+                + " (n+1)*fact FROM tempTable WHERE n < 9) SELECT fact FROM tempTable, User")
+                abstract public ${LifecyclesTypeNames.LIVE_DATA}<${CommonTypeNames.LIST}<Integer>>
+                getFactorialLiveData();
+                """) { parsedQuery, _ ->
+            assertThat(parsedQuery.query.tables, hasItem(Table("User", "User")))
+            assertThat(parsedQuery.query.tables,
+                    not(hasItem(Table("tempTable", "tempTable"))))
+            assertThat(parsedQuery.query.tables.size, `is`(1))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testLiveDataWithNothingToObserve() {
+        singleQueryMethod(
+                """
+                @Query("SELECT 1")
+                abstract public ${LifecyclesTypeNames.LIVE_DATA}<Integer> getOne();
+                """) { _, _ ->
+            // do nothing
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+    }
+
+    @Test
+    fun testLiveDataWithWithClauseAndNothingToObserve() {
+        singleQueryMethod(
+                """
+                @Query("WITH RECURSIVE tempTable(n, fact) AS (SELECT 0, 1 UNION ALL SELECT n+1,"
+                + " (n+1)*fact FROM tempTable WHERE n < 9) SELECT fact FROM tempTable")
+                abstract public ${LifecyclesTypeNames.LIVE_DATA}<${CommonTypeNames.LIST}<Integer>>
+                getFactorialLiveData();
+                """) { _, _ ->
+            // do nothing
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+    }
+
+    @Test
+    fun testBoundGeneric() {
+        singleQueryMethod(
+                """
+                static abstract class BaseModel<T> {
+                    @Query("select COUNT(*) from User")
+                    abstract public T getT();
+                }
+                @Dao
+                static abstract class ExtendingModel extends BaseModel<Integer> {
+                }
+                """) { parsedQuery, _ ->
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ClassName.get(Integer::class.java) as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testBoundGenericParameter() {
+        singleQueryMethod(
+                """
+                static abstract class BaseModel<T> {
+                    @Query("select COUNT(*) from User where :t")
+                    abstract public int getT(T t);
+                }
+                @Dao
+                static abstract class ExtendingModel extends BaseModel<Integer> {
+                }
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.parameters.first().type,
+                    `is`(invocation.processingEnv.elementUtils
+                            .getTypeElement("java.lang.Integer").asType()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testReadDeleteWithBadReturnType() {
+        singleQueryMethod(
+                """
+                @Query("DELETE from User where uid = :id")
+                abstract public float foo(int id);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
+        )
+    }
+
+    @Test
+    fun testSimpleDelete() {
+        singleQueryMethod(
+                """
+                @Query("DELETE from User where uid = :id")
+                abstract public int foo(int id);
+                """) { parsedQuery, _ ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.INT))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVoidDeleteQuery() {
+        singleQueryMethod(
+                """
+                @Query("DELETE from User where uid = :id")
+                abstract public void foo(int id);
+                """) { parsedQuery, _ ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.VOID))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVoidUpdateQuery() {
+        singleQueryMethod(
+                """
+                @Query("update user set name = :name")
+                abstract public void updateAllNames(String name);
+                """) { parsedQuery, invocation ->
+            assertThat(parsedQuery.name, `is`("updateAllNames"))
+            assertThat(parsedQuery.parameters.size, `is`(1))
+            assertThat(parsedQuery.returnType.typeName(), `is`(TypeName.VOID))
+            assertThat(parsedQuery.parameters.first().type.typeName(),
+                    `is`(invocation.context.COMMON_TYPES.STRING.typeName()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testLiveDataQuery() {
+        singleQueryMethod(
+                """
+                @Query("select name from user where uid = :id")
+                abstract ${LifecyclesTypeNames.LIVE_DATA}<String> nameLiveData(String id);
+                """
+        ) { parsedQuery, _ ->
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ParameterizedTypeName.get(LifecyclesTypeNames.LIVE_DATA,
+                            String::class.typeName()) as TypeName))
+            assertThat(parsedQuery.queryResultBinder,
+                    instanceOf(LiveDataQueryResultBinder::class.java))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testNonSelectLiveData() {
+        singleQueryMethod(
+                """
+                @Query("delete from user where uid = :id")
+                abstract ${LifecyclesTypeNames.LIVE_DATA}<Integer> deleteLiveData(String id);
+                """
+        ) { _, _ ->
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT)
+    }
+
+    @Test
+    fun testDataSourceFactoryQuery() {
+        singleQueryMethod(
+                """
+                @Query("select name from user")
+                abstract ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, String>
+                nameDataSourceFactory();
+                """
+        ) { parsedQuery, _ ->
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ParameterizedTypeName.get(PagingTypeNames.DATA_SOURCE_FACTORY,
+                            Integer::class.typeName(), String::class.typeName()) as TypeName))
+            assertThat(parsedQuery.queryResultBinder,
+                    instanceOf(DataSourceFactoryQueryResultBinder::class.java))
+            val tableNames =
+                    (parsedQuery.queryResultBinder as DataSourceFactoryQueryResultBinder)
+                            .positionalDataSourceQueryResultBinder.tableNames
+            assertEquals(setOf("user"), tableNames)
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMultiTableDataSourceFactoryQuery() {
+        singleQueryMethod(
+                """
+                @Query("select name from User u LEFT OUTER JOIN Book b ON u.uid == b.uid")
+                abstract ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, String>
+                nameDataSourceFactory();
+                """
+        ) { parsedQuery, _ ->
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ParameterizedTypeName.get(PagingTypeNames.DATA_SOURCE_FACTORY,
+                            Integer::class.typeName(), String::class.typeName()) as TypeName))
+            assertThat(parsedQuery.queryResultBinder,
+                    instanceOf(DataSourceFactoryQueryResultBinder::class.java))
+            val tableNames =
+                    (parsedQuery.queryResultBinder as DataSourceFactoryQueryResultBinder)
+                            .positionalDataSourceQueryResultBinder.tableNames
+            assertEquals(setOf("User", "Book"), tableNames)
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun query_detectTransaction_delete() {
+        singleQueryMethod(
+                """
+                @Query("delete from user where uid = :id")
+                abstract int deleteUser(String id);
+                """
+        ) { method, _ ->
+            assertThat(method.inTransaction, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun query_detectTransaction_update() {
+        singleQueryMethod(
+                """
+                @Query("UPDATE user set uid = :id + 1 where uid = :id")
+                abstract int incrementId(String id);
+                """
+        ) { method, _ ->
+            assertThat(method.inTransaction, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun query_detectTransaction_select() {
+        singleQueryMethod(
+                """
+                @Query("select * from user")
+                abstract int loadUsers();
+                """
+        ) { method, _ ->
+            assertThat(method.inTransaction, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun query_detectTransaction_selectInTransaction() {
+        singleQueryMethod(
+                """
+                @Transaction
+                @Query("select * from user")
+                abstract int loadUsers();
+                """
+        ) { method, _ ->
+            assertThat(method.inTransaction, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun skipVerification() {
+        singleQueryMethod(
+                """
+                @SkipQueryVerification
+                @Query("SELECT foo from User")
+                abstract public int[] foo();
+                """) { parsedQuery, _ ->
+            assertThat(parsedQuery.name, `is`("foo"))
+            assertThat(parsedQuery.parameters.size, `is`(0))
+            assertThat(parsedQuery.returnType.typeName(),
+                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun suppressWarnings() {
+        singleQueryMethod("""
+                @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
+                @Query("SELECT uid from User")
+                abstract public int[] foo();
+                """) { method, invocation ->
+            assertThat(QueryMethodProcessor(
+                    baseContext = invocation.context,
+                    containing = Mockito.mock(DeclaredType::class.java),
+                    executableElement = method.element,
+                    dbVerifier = null).context.logger.suppressedWarnings
+                    , `is`(setOf(Warning.CURSOR_MISMATCH)))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun pojo_renamedColumn() {
+        pojoTest("""
+                String name;
+                String lName;
+                """, listOf("name", "lastName as lName")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_exactMatch() {
+        pojoTest("""
+                String name;
+                String lastName;
+                """, listOf("name", "lastName")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_exactMatchWithStar() {
+        pojoTest("""
+            String name;
+            String lastName;
+            int uid;
+            @ColumnInfo(name = "ageColumn")
+            int age;
+        """, listOf("*")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_nonJavaName() {
+        pojoTest("""
+            @ColumnInfo(name = "MAX(ageColumn)")
+            int maxAge;
+            String name;
+            """, listOf("MAX(ageColumn)", "name")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_noMatchingFields() {
+        pojoTest("""
+                String nameX;
+                String lastNameX;
+                """, listOf("name", "lastName")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("name", "lastName")))
+            assertThat(adapter?.mapping?.unusedFields, `is`(adapter?.pojo?.fields))
+        }?.failsToCompile()
+                ?.withErrorContaining(CANNOT_FIND_QUERY_RESULT_ADAPTER)
+                ?.and()
+                ?.withWarningContaining(
+                        ProcessorErrors.cursorPojoMismatch(
+                                pojoTypeName = POJO,
+                                unusedColumns = listOf("name", "lastName"),
+                                unusedFields = listOf(createField("nameX"),
+                                        createField("lastNameX")),
+                                allColumns = listOf("name", "lastName"),
+                                allFields = listOf(
+                                        createField("nameX"),
+                                        createField("lastNameX")
+                                )
+                        )
+                )
+    }
+
+    @Test
+    fun pojo_badQuery() {
+        // do not report mismatch if query is broken
+        pojoTest("""
+            @ColumnInfo(name = "MAX(ageColumn)")
+            int maxAge;
+            String name;
+            """, listOf("MAX(age)", "name")) { _, _, _ ->
+        }?.failsToCompile()
+                ?.withErrorContaining("no such column: age")
+                ?.and()
+                ?.withErrorCount(1)
+                ?.withWarningCount(0)
+    }
+
+    @Test
+    fun pojo_tooManyColumns() {
+        pojoTest("""
+            String name;
+            String lastName;
+            """, listOf("uid", "name", "lastName")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("uid")))
+            assertThat(adapter?.mapping?.unusedFields, `is`(emptyList()))
+        }?.compilesWithoutError()?.withWarningContaining(
+                ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = POJO,
+                        unusedColumns = listOf("uid"),
+                        unusedFields = emptyList(),
+                        allColumns = listOf("uid", "name", "lastName"),
+                        allFields = listOf(createField("name"), createField("lastName"))
+                ))
+    }
+
+    @Test
+    fun pojo_tooManyFields() {
+        pojoTest("""
+            String name;
+            String lastName;
+            """, listOf("lastName")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(
+                    adapter?.pojo?.fields?.filter { it.name == "name" }
+            ))
+        }?.compilesWithoutError()?.withWarningContaining(
+                ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = POJO,
+                        unusedColumns = emptyList(),
+                        unusedFields = listOf(createField("name")),
+                        allColumns = listOf("lastName"),
+                        allFields = listOf(createField("name"), createField("lastName"))
+                ))
+    }
+
+    @Test
+    fun pojo_missingNonNull() {
+        pojoTest("""
+            @NonNull
+            String name;
+            String lastName;
+            """, listOf("lastName")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(emptyList()))
+            assertThat(adapter?.mapping?.unusedFields, `is`(
+                    adapter?.pojo?.fields?.filter { it.name == "name" }
+            ))
+        }?.failsToCompile()?.withWarningContaining(
+                ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = POJO,
+                        unusedColumns = emptyList(),
+                        unusedFields = listOf(createField("name")),
+                        allColumns = listOf("lastName"),
+                        allFields = listOf(createField("name"), createField("lastName"))
+                ))?.and()?.withErrorContaining(
+                ProcessorErrors.pojoMissingNonNull(pojoTypeName = POJO,
+                        missingPojoFields = listOf("name"),
+                        allQueryColumns = listOf("lastName")))
+    }
+
+    @Test
+    fun pojo_tooManyFieldsAndColumns() {
+        pojoTest("""
+            String name;
+            String lastName;
+            """, listOf("uid", "name")) { adapter, _, _ ->
+            assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("uid")))
+            assertThat(adapter?.mapping?.unusedFields, `is`(
+                    adapter?.pojo?.fields?.filter { it.name == "lastName" }
+            ))
+        }?.compilesWithoutError()?.withWarningContaining(
+                ProcessorErrors.cursorPojoMismatch(
+                        pojoTypeName = POJO,
+                        unusedColumns = listOf("uid"),
+                        unusedFields = listOf(createField("lastName")),
+                        allColumns = listOf("uid", "name"),
+                        allFields = listOf(createField("name"), createField("lastName"))
+                ))
+    }
+
+    fun pojoTest(pojoFields: String, queryColumns: List<String>,
+                 handler: (PojoRowAdapter?, QueryMethod, TestInvocation) -> Unit): CompileTester? {
+        val assertion = singleQueryMethod(
+                """
+                static class Pojo {
+                    $pojoFields
+                }
+                @Query("SELECT ${queryColumns.joinToString(", ")} from User LIMIT 1")
+                abstract MyClass.Pojo getNameAndLastNames();
+                """
+        ) { parsedQuery, invocation ->
+            val adapter = parsedQuery.queryResultBinder.adapter
+            if (enableVerification) {
+                if (adapter is SingleEntityQueryResultAdapter) {
+                    handler(adapter.rowAdapter as? PojoRowAdapter, parsedQuery, invocation)
+                } else {
+                    handler(null, parsedQuery, invocation)
+                }
+            } else {
+                assertThat(adapter, nullValue())
+            }
+        }
+        if (enableVerification) {
+            return assertion
+        } else {
+            assertion.failsToCompile().withErrorContaining(CANNOT_FIND_QUERY_RESULT_ADAPTER)
+            return null
+        }
+    }
+
+    fun singleQueryMethod(vararg input: String,
+                          handler: (QueryMethod, TestInvocation) -> Unit):
+            CompileTester {
+        return assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER, COMMON.BOOK))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
+                                Entity::class, PrimaryKey::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            it.hasAnnotation(Query::class)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val verifier = if (enableVerification) {
+                                createVerifierFromEntities(invocation)
+                            } else {
+                                null
+                            }
+                            val parser = QueryMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()),
+                                    dbVerifier = verifier)
+                            val parsedQuery = parser.process()
+                            handler(parsedQuery, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
new file mode 100644
index 0000000..d6d6269
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2018 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 androidx.room.processor
+
+import COMMON
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.RawQuery
+import androidx.room.ext.PagingTypeNames
+import androidx.room.ext.SupportDbTypeNames
+import androidx.room.ext.hasAnnotation
+import androidx.room.ext.typeName
+import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.RawQueryMethod
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+
+class RawQueryMethodProcessorTest {
+    @Test
+    fun supportRawQuery() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public int[] foo(SupportSQLiteQuery query);
+                """) { query, _ ->
+            assertThat(query.name, `is`("foo"))
+            assertThat(query.runtimeQueryParam, `is`(
+                    RawQueryMethod.RuntimeQueryParameter(
+                            paramName = "query",
+                            type = SupportDbTypeNames.QUERY
+                    )
+            ))
+            assertThat(query.returnType.typeName(),
+                    `is`(ArrayTypeName.of(TypeName.INT) as TypeName))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun stringRawQuery() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public int[] foo(String query);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(RAW_QUERY_STRING_PARAMETER_REMOVED)
+    }
+
+    @Test
+    fun withObservedEntities() {
+        singleQueryMethod(
+                """
+                @RawQuery(observedEntities = User.class)
+                abstract public LiveData<User> foo(SupportSQLiteQuery query);
+                """) { query, _ ->
+            assertThat(query.name, `is`("foo"))
+            assertThat(query.runtimeQueryParam, `is`(
+                    RawQueryMethod.RuntimeQueryParameter(
+                            paramName = "query",
+                            type = SupportDbTypeNames.QUERY
+                    )
+            ))
+            assertThat(query.observedTableNames.size, `is`(1))
+            assertThat(query.observedTableNames, `is`(setOf("User")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun observableWithoutEntities() {
+        singleQueryMethod(
+                """
+                @RawQuery(observedEntities = {})
+                abstract public LiveData<User> foo(SupportSQLiteQuery query);
+                """) { query, _ ->
+            assertThat(query.name, `is`("foo"))
+            assertThat(query.runtimeQueryParam, `is`(
+                    RawQueryMethod.RuntimeQueryParameter(
+                            paramName = "query",
+                            type = SupportDbTypeNames.QUERY
+                    )
+            ))
+            assertThat(query.observedTableNames, `is`(emptySet()))
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+    }
+
+    @Test
+    fun observableWithoutEntities_dataSourceFactory() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public ${PagingTypeNames.DATA_SOURCE_FACTORY}<Integer, User> getOne();
+                """) { _, _ ->
+            // do nothing
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+    }
+
+    @Test
+    fun observableWithoutEntities_positionalDataSource() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne();
+                """) { _, _ ->
+            // do nothing
+        }.failsToCompile()
+                .withErrorContaining(ProcessorErrors.OBSERVABLE_QUERY_NOTHING_TO_OBSERVE)
+    }
+
+    @Test
+    fun positionalDataSource() {
+        singleQueryMethod(
+                """
+                @RawQuery(observedEntities = {User.class})
+                abstract public ${PagingTypeNames.POSITIONAL_DATA_SOURCE}<User> getOne(
+                        SupportSQLiteQuery query);
+                """) { _, _ ->
+            // do nothing
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun pojo() {
+        val pojo: TypeName = ClassName.get("foo.bar.MyClass", "MyPojo")
+        singleQueryMethod(
+                """
+                public class MyPojo {
+                    public String foo;
+                    public String bar;
+                }
+
+                @RawQuery
+                abstract public MyPojo foo(SupportSQLiteQuery query);
+                """) { query, _ ->
+            assertThat(query.name, `is`("foo"))
+            assertThat(query.runtimeQueryParam, `is`(
+                    RawQueryMethod.RuntimeQueryParameter(
+                            paramName = "query",
+                            type = SupportDbTypeNames.QUERY
+                    )
+            ))
+            assertThat(query.returnType.typeName(), `is`(pojo))
+            assertThat(query.observedTableNames, `is`(emptySet()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun void() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public void foo(SupportSQLiteQuery query);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE
+        )
+    }
+
+    @Test
+    fun noArgs() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public int[] foo();
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RAW_QUERY_BAD_PARAMS
+        )
+    }
+
+    @Test
+    fun tooManyArgs() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public int[] foo(SupportSQLiteQuery query,
+                                          SupportSQLiteQuery query2);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RAW_QUERY_BAD_PARAMS
+        )
+    }
+
+    @Test
+    fun varargs() {
+        singleQueryMethod(
+                """
+                @RawQuery
+                abstract public int[] foo(SupportSQLiteQuery... query);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.RAW_QUERY_BAD_PARAMS
+        )
+    }
+
+    @Test
+    fun observed_notAnEntity() {
+        singleQueryMethod(
+                """
+                @RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class})
+                abstract public int[] foo(SupportSQLiteQuery query);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.rawQueryBadEntity(COMMON.NOT_AN_ENTITY_TYPE_NAME)
+        )
+    }
+
+    @Test
+    fun observed_relationPojo() {
+        singleQueryMethod(
+                """
+                public static class MyPojo {
+                    public String foo;
+                    @Relation(
+                        parentColumn = "foo",
+                        entityColumn = "name"
+                    )
+                    public java.util.List<User> users;
+                }
+                @RawQuery(observedEntities = MyPojo.class)
+                abstract public int[] foo(SupportSQLiteQuery query);
+                """) { method, _ ->
+            assertThat(method.observedTableNames, `is`(setOf("User")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun observed_embedded() {
+        singleQueryMethod(
+                """
+                public static class MyPojo {
+                    public String foo;
+                    @Embedded
+                    public User users;
+                }
+                @RawQuery(observedEntities = MyPojo.class)
+                abstract public int[] foo(SupportSQLiteQuery query);
+                """) { method, _ ->
+            assertThat(method.observedTableNames, `is`(setOf("User")))
+        }.compilesWithoutError()
+    }
+
+    private fun singleQueryMethod(
+            vararg input: String,
+            handler: (RawQueryMethod, TestInvocation) -> Unit
+    ): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX
+                                + input.joinToString("\n")
+                                + DAO_SUFFIX
+                ), COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.USER,
+                        COMMON.DATA_SOURCE_FACTORY, COMMON.POSITIONAL_DATA_SOURCE,
+                        COMMON.NOT_AN_ENTITY))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Query::class, Dao::class, ColumnInfo::class,
+                                Entity::class, PrimaryKey::class, RawQuery::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            it.hasAnnotation(RawQuery::class)
+                                                        }
+                                        )
+                                    }.first { it.second.isNotEmpty() }
+                            val parser = RawQueryMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            val parsedQuery = parser.process()
+                            handler(parsedQuery, invocation)
+                            true
+                        }
+                        .build())
+    }
+
+    companion object {
+        private const val DAO_PREFIX = """
+                package foo.bar;
+                import androidx.annotation.NonNull;
+                import androidx.room.*;
+                import androidx.sqlite.db.SupportSQLiteQuery;
+                import androidx.lifecycle.LiveData;
+                @Dao
+                abstract class MyClass {
+                """
+        private const val DAO_SUFFIX = "}"
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
new file mode 100644
index 0000000..f771b28
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
@@ -0,0 +1,298 @@
+/*
+ * 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 androidx.room.processor
+
+import COMMON
+import androidx.room.Dao
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.typeName
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.ShortcutMethod
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+import kotlin.reflect.KClass
+
+/**
+ * Base test class for shortcut methods.
+ */
+abstract class ShortcutMethodProcessorTest<out T : ShortcutMethod>(
+        val annotation: KClass<out Annotation>) {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import androidx.room.*;
+                import java.util.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
+        val BOOK_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Book")
+    }
+
+    @Test
+    fun noParams() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo();
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("foo"))
+            assertThat(shortcut.parameters.size, `is`(0))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.failsToCompile().withErrorContaining(noParamsError())
+    }
+
+    abstract fun noParamsError(): String
+
+    @Test
+    fun single() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public int foo(User user);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("foo"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["user"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun notAnEntity() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo(NotAnEntity notValid);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("foo"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.entityType, `is`(CoreMatchers.nullValue()))
+            assertThat(shortcut.entities.size, `is`(0))
+        }.failsToCompile().withErrorContaining(
+                ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
+        )
+    }
+
+    @Test
+    fun two() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo(User u1, User u2);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("foo"))
+
+            assertThat(shortcut.parameters.size, `is`(2))
+            shortcut.parameters.forEach {
+                assertThat(it.type.typeName(), `is`(USER_TYPE_NAME))
+                assertThat(it.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            }
+            assertThat(shortcut.entities.size, `is`(2))
+            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.parameters.map { it.name },
+                    `is`(listOf("u1", "u2")))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun list() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public int users(List<User> users);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("users"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(
+                            ClassName.get("java.util", "List"), USER_TYPE_NAME) as TypeName))
+            assertThat(param.entityType?.typeName(), `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun array() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void users(User[] users);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("users"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun set() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void modifyUsers(Set<User> users);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("modifyUsers"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.util", "Set")
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun iterable() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void modifyUsers(Iterable<User> users);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("modifyUsers"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("java.lang", "Iterable")
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun customCollection() {
+        singleShortcutMethod(
+                """
+                static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
+                @${annotation.java.canonicalName}
+                abstract public void modifyUsers(MyList<String, User> users);
+                """) { shortcut, _ ->
+            assertThat(shortcut.name, `is`("modifyUsers"))
+            assertThat(shortcut.parameters.size, `is`(1))
+            val param = shortcut.parameters.first()
+            assertThat(param.type.typeName(), `is`(
+                    ParameterizedTypeName.get(ClassName.get("foo.bar", "MyClass.MyList")
+                            , CommonTypeNames.STRING
+                            , COMMON.USER_TYPE_NAME) as TypeName))
+            assertThat(shortcut.entities.size, `is`(1))
+            assertThat(shortcut.entities["users"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.returnCount, `is`(false))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun differentTypes() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public void foo(User u1, Book b1);
+                """) { shortcut, _ ->
+            assertThat(shortcut.parameters.size, `is`(2))
+            assertThat(shortcut.parameters[0].type.typeName().toString(),
+                    `is`("foo.bar.User"))
+            assertThat(shortcut.parameters[1].type.typeName().toString(),
+                    `is`("foo.bar.Book"))
+            assertThat(shortcut.parameters.map { it.name }, `is`(listOf("u1", "b1")))
+            assertThat(shortcut.returnCount, `is`(false))
+            assertThat(shortcut.entities.size, `is`(2))
+            assertThat(shortcut.entities["u1"]?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(shortcut.entities["b1"]?.typeName, `is`(BOOK_TYPE_NAME))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun invalidReturnType() {
+        singleShortcutMethod(
+                """
+                @${annotation.java.canonicalName}
+                abstract public long foo(User user);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(invalidReturnTypeError())
+    }
+
+    abstract fun invalidReturnTypeError(): String
+
+    abstract fun process(baseContext: Context, containing: DeclaredType,
+                         executableElement: ExecutableElement): T
+
+    fun singleShortcutMethod(vararg input: String,
+                             handler: (T, TestInvocation) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ), COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(annotation, Dao::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            MoreElements.isAnnotationPresent(it,
+                                                                    annotation.java)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val processed = process(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            handler(processed, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
new file mode 100644
index 0000000..d0c4fe3
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.Dao
+import androidx.room.Transaction
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.TransactionMethod
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class TransactionMethodProcessorTest {
+
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import androidx.room.*;
+                import java.util.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+    }
+
+    @Test
+    fun simple() {
+        singleTransactionMethod(
+                """
+                @Transaction
+                public String doInTransaction(int param) { return null; }
+                """) { transaction, _ ->
+            assertThat(transaction.name, `is`("doInTransaction"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun modifier_private() {
+        singleTransactionMethod(
+                """
+                @Transaction
+                private String doInTransaction(int param) { return null; }
+                """) { transaction, _ ->
+            assertThat(transaction.name, `is`("doInTransaction"))
+        }.failsToCompile().withErrorContaining(ProcessorErrors.TRANSACTION_METHOD_MODIFIERS)
+    }
+
+    @Test
+    fun modifier_final() {
+        singleTransactionMethod(
+                """
+                @Transaction
+                public final String doInTransaction(int param) { return null; }
+                """) { transaction, _ ->
+            assertThat(transaction.name, `is`("doInTransaction"))
+        }.failsToCompile().withErrorContaining(ProcessorErrors.TRANSACTION_METHOD_MODIFIERS)
+    }
+
+    private fun singleTransactionMethod(vararg input: String,
+                                handler: (TransactionMethod, TestInvocation) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        TransactionMethodProcessorTest.DAO_PREFIX + input.joinToString("\n") +
+                                TransactionMethodProcessorTest.DAO_SUFFIX
+                )))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Transaction::class, Dao::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            MoreElements.isAnnotationPresent(it,
+                                                                    Transaction::class.java)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val processor = TransactionMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            val processed = processor.process()
+                            handler(processed, invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/UpdateMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/UpdateMethodProcessorTest.kt
new file mode 100644
index 0000000..75366e2
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/UpdateMethodProcessorTest.kt
@@ -0,0 +1,64 @@
+/*
+ * 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 androidx.room.processor
+
+import androidx.room.OnConflictStrategy
+import androidx.room.Update
+import androidx.room.processor.ProcessorErrors
+        .UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
+import androidx.room.processor.ProcessorErrors.UPDATE_MISSING_PARAMS
+import androidx.room.vo.UpdateMethod
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.type.DeclaredType
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class UpdateMethodProcessorTest : ShortcutMethodProcessorTest<UpdateMethod>(Update::class) {
+    override fun invalidReturnTypeError(): String = UPDATE_METHODS_MUST_RETURN_VOID_OR_INT
+
+    override fun noParamsError(): String = UPDATE_MISSING_PARAMS
+
+    override fun process(baseContext: Context, containing: DeclaredType,
+                         executableElement: ExecutableElement): UpdateMethod {
+        return UpdateMethodProcessor(baseContext, containing, executableElement).process()
+    }
+
+    @Test
+    fun goodConflict() {
+        singleShortcutMethod(
+                """
+                @Update(onConflict = OnConflictStrategy.REPLACE)
+                abstract public void foo(User user);
+                """) { shortcut, _ ->
+            assertThat(shortcut.onConflictStrategy, `is`(OnConflictStrategy.REPLACE))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun badConflict() {
+        singleShortcutMethod(
+                """
+                @Update(onConflict = -1)
+                abstract public void foo(User user);
+                """) { _, _ ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/BasicColumnTypeAdaptersTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/BasicColumnTypeAdaptersTest.kt
new file mode 100644
index 0000000..4d2f940
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/BasicColumnTypeAdaptersTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver
+
+import androidx.room.processor.Context
+import androidx.room.testing.TestInvocation
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import simpleRun
+import testCodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@RunWith(Parameterized::class)
+class BasicColumnTypeAdaptersTest(val input: Input, val bindCode: String,
+                                  val cursorCode: String) {
+    val scope = testCodeGenScope()
+
+    companion object {
+        val SQLITE_STMT: TypeName = ClassName.get("android.database.sqlite", "SQLiteStatement")
+        val CURSOR: TypeName = ClassName.get("android.database", "Cursor")
+
+        @Parameterized.Parameters(name = "kind:{0},bind:_{1},cursor:_{2}")
+        @JvmStatic
+        fun params(): List<Array<Any>> {
+            return listOf(
+                    arrayOf(Input(TypeKind.INT),
+                            "st.bindLong(6, inp);",
+                            "out = crs.getInt(9);"),
+                    arrayOf(Input(TypeKind.BYTE),
+                            "st.bindLong(6, inp);",
+                            "out = (byte) crs.getShort(9);"),
+                    arrayOf(Input(TypeKind.SHORT),
+                            "st.bindLong(6, inp);",
+                            "out = crs.getShort(9);"),
+                    arrayOf(Input(TypeKind.LONG),
+                            "st.bindLong(6, inp);",
+                            "out = crs.getLong(9);"),
+                    arrayOf(Input(TypeKind.CHAR),
+                            "st.bindLong(6, inp);",
+                            "out = (char) crs.getInt(9);"),
+                    arrayOf(Input(TypeKind.FLOAT),
+                            "st.bindDouble(6, inp);",
+                            "out = crs.getFloat(9);"),
+                    arrayOf(Input(TypeKind.DOUBLE),
+                            "st.bindDouble(6, inp);",
+                            "out = crs.getDouble(9);"),
+                    arrayOf(Input(TypeKind.DECLARED, "java.lang.String"),
+                            """
+                            if (inp == null) {
+                              st.bindNull(6);
+                            } else {
+                              st.bindString(6, inp);
+                            }
+                            """.trimIndent(),
+                            "out = crs.getString(9);")
+            )
+        }
+    }
+
+    @Test
+    fun bind() {
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
+            adapter.bindToStmt("st", "6", "inp", scope)
+            assertThat(scope.generate().trim(), `is`(bindCode))
+            generateCode(invocation, false)
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun boxedBind() {
+        if (!input.typeKind.isPrimitive) {
+            return // no-op for those
+        }
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(
+                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
+            adapter.bindToStmt("st", "6", "inp", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    if (inp == null) {
+                      st.bindNull(6);
+                    } else {
+                      $bindCode
+                    }
+                    """.trimIndent()
+            ))
+            generateCode(invocation, true)
+        }.compilesWithoutError()
+    }
+
+    private fun generateCode(invocation: TestInvocation, boxed: Boolean) {
+        val typeMirror = if (boxed) input.getBoxedTypeMirror(invocation.processingEnv)
+        else input.getTypeMirror(invocation.processingEnv)
+        val spec = TypeSpec.classBuilder("OutClass")
+                .addField(FieldSpec.builder(SQLITE_STMT, "st").build())
+                .addField(FieldSpec.builder(CURSOR, "crs").build())
+                .addField(FieldSpec.builder(TypeName.get(typeMirror), "out").build())
+                .addField(FieldSpec.builder(TypeName.get(typeMirror), "inp").build())
+                .addMethod(
+                        MethodSpec.methodBuilder("foo")
+                                .addCode(scope.builder().build())
+                                .build()
+                )
+                .build()
+        JavaFile.builder("foo.bar", spec).build().writeTo(invocation.processingEnv.filer)
+    }
+
+    @Test
+    fun read() {
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(input.getTypeMirror(invocation.processingEnv), null)!!
+            adapter.readFromCursor("out", "crs", "9", scope)
+            assertThat(scope.generate().trim(), `is`(cursorCode))
+            generateCode(invocation, false)
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun readBoxed() {
+        if (!input.typeKind.isPrimitive) {
+            return // no-op for those
+        }
+        simpleRun { invocation ->
+            val adapter = TypeAdapterStore.create(Context(invocation.processingEnv))
+                    .findColumnTypeAdapter(
+                            input.getBoxedTypeMirror(invocation.processingEnv), null)!!
+            adapter.readFromCursor("out", "crs", "9", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    if (crs.isNull(9)) {
+                      out = null;
+                    } else {
+                      $cursorCode
+                    }
+                    """.trimIndent()
+            ))
+            generateCode(invocation, true)
+        }.compilesWithoutError()
+    }
+
+    data class Input(val typeKind: TypeKind, val qName: String? = null) {
+        fun getTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
+            return if (typeKind.isPrimitive) {
+                processingEnv.typeUtils.getPrimitiveType(typeKind)
+            } else {
+                processingEnv.elementUtils.getTypeElement(qName).asType()
+            }
+        }
+
+        fun getBoxedTypeMirror(processingEnv: ProcessingEnvironment): TypeMirror {
+            return if (typeKind.isPrimitive) {
+                processingEnv.typeUtils
+                        .boxedClass(getTypeMirror(processingEnv) as PrimitiveType)
+                        .asType()
+            } else {
+                getTypeMirror(processingEnv)
+            }
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
new file mode 100644
index 0000000..196fc41
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/CustomTypeConverterResolutionTest.kt
@@ -0,0 +1,354 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("HasPlatformType")
+
+package androidx.room.solver
+
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.RoomProcessor
+import androidx.room.TypeConverter
+import androidx.room.TypeConverters
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.processor.ProcessorErrors.CANNOT_BIND_QUERY_PARAMETER_INTO_STMT
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.AnnotationSpec
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.Modifier
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class CustomTypeConverterResolutionTest {
+    fun TypeSpec.toJFO(): JavaFileObject {
+        return JavaFileObjects.forSourceString("foo.bar.${this.name}",
+                "package foo.bar;\n" + toString())
+    }
+
+    companion object {
+        val ENTITY = ClassName.get("foo.bar", "MyEntity")
+        val DB = ClassName.get("foo.bar", "MyDb")
+        val DAO = ClassName.get("foo.bar", "MyDao")
+
+        val CUSTOM_TYPE = ClassName.get("foo.bar", "CustomType")
+        val CUSTOM_TYPE_JFO = JavaFileObjects.forSourceLines(CUSTOM_TYPE.toString(),
+                """
+                package ${CUSTOM_TYPE.packageName()};
+                public class ${CUSTOM_TYPE.simpleName()} {
+                    public int value;
+                }
+                """)
+        val CUSTOM_TYPE_CONVERTER = ClassName.get("foo.bar", "MyConverter")
+        val CUSTOM_TYPE_CONVERTER_JFO = JavaFileObjects.forSourceLines(
+                CUSTOM_TYPE_CONVERTER.toString(),
+                """
+                package ${CUSTOM_TYPE_CONVERTER.packageName()};
+                public class ${CUSTOM_TYPE_CONVERTER.simpleName()} {
+                    @${TypeConverter::class.java.canonicalName}
+                    public static $CUSTOM_TYPE toCustom(int value) {
+                        return null;
+                    }
+                    @${TypeConverter::class.java.canonicalName}
+                    public static int fromCustom($CUSTOM_TYPE input) {
+                        return 0;
+                    }
+                }
+                """)
+        val CUSTOM_TYPE_SET = ParameterizedTypeName.get(
+                ClassName.get(Set::class.java), CUSTOM_TYPE)
+        val CUSTOM_TYPE_SET_CONVERTER = ClassName.get("foo.bar", "MySetConverter")
+        val CUSTOM_TYPE_SET_CONVERTER_JFO = JavaFileObjects.forSourceLines(
+                CUSTOM_TYPE_SET_CONVERTER.toString(),
+                """
+                package ${CUSTOM_TYPE_SET_CONVERTER.packageName()};
+                import java.util.HashSet;
+                import java.util.Set;
+                public class ${CUSTOM_TYPE_SET_CONVERTER.simpleName()} {
+                    @${TypeConverter::class.java.canonicalName}
+                    public static $CUSTOM_TYPE_SET toCustom(int value) {
+                        return null;
+                    }
+                    @${TypeConverter::class.java.canonicalName}
+                    public static int fromCustom($CUSTOM_TYPE_SET input) {
+                        return 0;
+                    }
+                }
+                """)
+    }
+
+    @Test
+    fun useFromDatabase_forEntity() {
+        val entity = createEntity(hasCustomField = true)
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true, hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun collection_forEntity() {
+        val entity = createEntity(
+                hasCustomField = true,
+                useCollection = true)
+        val database = createDatabase(
+                hasConverters = true,
+                hasDao = true,
+                useCollection = true)
+        val dao = createDao(
+                hasQueryWithCustomParam = false,
+                useCollection = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun collection_forDao() {
+        val entity = createEntity(
+                hasCustomField = true,
+                useCollection = true)
+        val database = createDatabase(
+                hasConverters = true,
+                hasDao = true,
+                useCollection = true)
+        val dao = createDao(
+                hasQueryWithCustomParam = true,
+                useCollection = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDatabase_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDatabase_forReturnValue() {
+        val entity = createEntity(hasCustomField = true)
+        val database = createDatabase(hasConverters = true, hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun useFromDao_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasConverters = true, hasQueryReturningEntity = true,
+                hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntity_forReturnValue() {
+        val entity = createEntity(hasCustomField = true, hasConverters = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntityField_forReturnValue() {
+        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryReturningEntity = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun useFromEntity_forQueryParameter() {
+        val entity = createEntity(hasCustomField = true, hasConverters = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO())
+                .failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+    }
+
+    @Test
+    fun useFromEntityField_forQueryParameter() {
+        val entity = createEntity(hasCustomField = true, hasConverterOnField = true)
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO())
+                .failsToCompile().withErrorContaining(CANNOT_BIND_QUERY_PARAMETER_INTO_STMT)
+    }
+
+    @Test
+    fun useFromQueryMethod_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true, hasMethodConverters = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    @Test
+    fun useFromQueryParameter_forQueryParameter() {
+        val entity = createEntity()
+        val database = createDatabase(hasDao = true)
+        val dao = createDao(hasQueryWithCustomParam = true, hasParameterConverters = true)
+        run(entity.toJFO(), dao.toJFO(), database.toJFO()).compilesWithoutError()
+    }
+
+    fun run(vararg jfos: JavaFileObject): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfos.toList() + CUSTOM_TYPE_JFO + CUSTOM_TYPE_CONVERTER_JFO
+                        + CUSTOM_TYPE_SET_CONVERTER_JFO)
+                .processedWith(RoomProcessor())
+    }
+
+    private fun createEntity(
+            hasCustomField: Boolean = false,
+            hasConverters: Boolean = false,
+            hasConverterOnField: Boolean = false,
+            useCollection: Boolean = false): TypeSpec {
+        if (hasConverterOnField && hasConverters) {
+            throw IllegalArgumentException("cannot have both converters")
+        }
+        val type = if (useCollection) {
+            CUSTOM_TYPE_SET
+        } else {
+            CUSTOM_TYPE
+        }
+        return TypeSpec.classBuilder(ENTITY).apply {
+            addAnnotation(Entity::class.java)
+            addModifiers(Modifier.PUBLIC)
+            if (hasCustomField) {
+                addField(FieldSpec.builder(type, "myCustomField", Modifier.PUBLIC).apply {
+                    if (hasConverterOnField) {
+                        addAnnotation(createConvertersAnnotation())
+                    }
+                }.build())
+            }
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation())
+            }
+            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
+                addAnnotation(PrimaryKey::class.java)
+            }.build())
+        }.build()
+    }
+
+    private fun createDatabase(
+            hasConverters: Boolean = false,
+            hasDao: Boolean = false,
+            useCollection: Boolean = false): TypeSpec {
+        return TypeSpec.classBuilder(DB).apply {
+            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
+            superclass(RoomTypeNames.ROOM_DB)
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation(useCollection = useCollection))
+            }
+            addField(FieldSpec.builder(TypeName.INT, "id", Modifier.PUBLIC).apply {
+                addAnnotation(PrimaryKey::class.java)
+            }.build())
+            if (hasDao) {
+                addMethod(MethodSpec.methodBuilder("getDao").apply {
+                    addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
+                    returns(DAO)
+                }.build())
+            }
+            addAnnotation(
+                    AnnotationSpec.builder(Database::class.java).apply {
+                        addMember("entities", "{$T.class}", ENTITY)
+                        addMember("version", "42")
+                    }.build()
+            )
+        }.build()
+    }
+
+    private fun createDao(
+            hasConverters: Boolean = false,
+            hasQueryReturningEntity: Boolean = false,
+            hasQueryWithCustomParam: Boolean = false,
+            hasMethodConverters: Boolean = false,
+            hasParameterConverters: Boolean = false,
+            useCollection: Boolean = false): TypeSpec {
+        val annotationCount = listOf(hasMethodConverters, hasConverters, hasParameterConverters)
+                .map { if (it) 1 else 0 }.sum()
+        if (annotationCount > 1) {
+            throw IllegalArgumentException("cannot set both of these")
+        }
+        if (hasParameterConverters && !hasQueryWithCustomParam) {
+            throw IllegalArgumentException("inconsistent")
+        }
+        return TypeSpec.classBuilder(DAO).apply {
+            addAnnotation(Dao::class.java)
+            addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
+            if (hasConverters) {
+                addAnnotation(createConvertersAnnotation(useCollection = useCollection))
+            }
+            if (hasQueryReturningEntity) {
+                addMethod(MethodSpec.methodBuilder("loadAll").apply {
+                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
+                        addMember("value", S, "SELECT * FROM ${ENTITY.simpleName()} LIMIT 1")
+                    }.build())
+                    addModifiers(Modifier.ABSTRACT)
+                    returns(ENTITY)
+                }.build())
+            }
+            val customType = if (useCollection) {
+                CUSTOM_TYPE_SET
+            } else {
+                CUSTOM_TYPE
+            }
+            if (hasQueryWithCustomParam) {
+                addMethod(MethodSpec.methodBuilder("queryWithCustom").apply {
+                    addAnnotation(AnnotationSpec.builder(Query::class.java).apply {
+                        addMember("value", S, "SELECT COUNT(*) FROM ${ENTITY.simpleName()} where" +
+                                " id = :custom")
+                    }.build())
+                    if (hasMethodConverters) {
+                        addAnnotation(createConvertersAnnotation(useCollection = useCollection))
+                    }
+                    addParameter(ParameterSpec.builder(customType, "custom").apply {
+                        if (hasParameterConverters) {
+                            addAnnotation(createConvertersAnnotation(useCollection = useCollection))
+                        }
+                    }.build())
+                    addModifiers(Modifier.ABSTRACT)
+                    returns(TypeName.INT)
+                }.build())
+            }
+        }.build()
+    }
+
+    private fun createConvertersAnnotation(useCollection: Boolean = false): AnnotationSpec {
+        val converter = if (useCollection) {
+            CUSTOM_TYPE_SET_CONVERTER
+        } else {
+            CUSTOM_TYPE_CONVERTER
+        }
+        return AnnotationSpec.builder(TypeConverters::class.java)
+                .addMember("value", "$T.class", converter).build()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
new file mode 100644
index 0000000..c512c21
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver
+
+import COMMON
+import androidx.paging.DataSource
+import androidx.paging.PositionalDataSource
+import androidx.room.Entity
+import androidx.room.ext.L
+import androidx.room.ext.LifecyclesTypeNames
+import androidx.room.ext.PagingTypeNames
+import androidx.room.ext.ReactiveStreamsTypeNames
+import androidx.room.ext.RoomTypeNames.STRING_UTIL
+import androidx.room.ext.RxJava2TypeNames
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
+import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
+import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider
+import androidx.room.solver.binderprovider.FlowableQueryResultBinderProvider
+import androidx.room.solver.binderprovider.LiveDataQueryResultBinderProvider
+import androidx.room.solver.types.CompositeAdapter
+import androidx.room.solver.types.TypeConverter
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import simpleRun
+import testCodeGenScope
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.type.TypeKind
+
+@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+@RunWith(JUnit4::class)
+class TypeAdapterStoreTest {
+    companion object {
+        fun tmp(index: Int) = CodeGenScope._tmpVar(index)
+    }
+
+    @Test
+    fun testDirect() {
+        singleRun { invocation ->
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
+            val primitiveType = invocation.processingEnv.typeUtils.getPrimitiveType(TypeKind.INT)
+            val adapter = store.findColumnTypeAdapter(primitiveType, null)
+            assertThat(adapter, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testJavaLangBoolean() {
+        singleRun { invocation ->
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
+            val boolean = invocation
+                    .processingEnv
+                    .elementUtils
+                    .getTypeElement("java.lang.Boolean")
+                    .asType()
+            val adapter = store.findColumnTypeAdapter(boolean, null)
+            assertThat(adapter, notNullValue())
+            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
+            val composite = adapter as CompositeAdapter
+            assertThat(composite.intoStatementConverter?.from?.typeName(),
+                    `is`(TypeName.BOOLEAN.box()))
+            assertThat(composite.columnTypeAdapter.out.typeName(),
+                    `is`(TypeName.INT.box()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVia1TypeAdapter() {
+        singleRun { invocation ->
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv))
+            val booleanType = invocation.processingEnv.typeUtils
+                    .getPrimitiveType(TypeKind.BOOLEAN)
+            val adapter = store.findColumnTypeAdapter(booleanType, null)
+            assertThat(adapter, notNullValue())
+            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
+            val bindScope = testCodeGenScope()
+            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    ${tmp(0)} = fooVar ? 1 : 0;
+                    stmt.bindLong(41, ${tmp(0)});
+                    """.trimIndent()))
+
+            val cursorScope = testCodeGenScope()
+            adapter.readFromCursor("res", "curs", "7", cursorScope)
+            assertThat(cursorScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    ${tmp(0)} = curs.getInt(7);
+                    res = ${tmp(0)} != 0;
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testVia2TypeAdapters() {
+        singleRun { invocation ->
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv),
+                    pointTypeConverters(invocation.processingEnv))
+            val pointType = invocation.processingEnv.elementUtils
+                    .getTypeElement("foo.bar.Point").asType()
+            val adapter = store.findColumnTypeAdapter(pointType, null)
+            assertThat(adapter, notNullValue())
+            assertThat(adapter, instanceOf(CompositeAdapter::class.java))
+
+            val bindScope = testCodeGenScope()
+            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    final boolean ${tmp(1)};
+                    ${tmp(1)} = foo.bar.Point.toBoolean(fooVar);
+                    ${tmp(0)} = ${tmp(1)} ? 1 : 0;
+                    stmt.bindLong(41, ${tmp(0)});
+                    """.trimIndent()))
+
+            val cursorScope = testCodeGenScope()
+            adapter.readFromCursor("res", "curs", "11", cursorScope).toString()
+            assertThat(cursorScope.generate().trim(), `is`("""
+                    final int ${tmp(0)};
+                    ${tmp(0)} = curs.getInt(11);
+                    final boolean ${tmp(1)};
+                    ${tmp(1)} = ${tmp(0)} != 0;
+                    res = foo.bar.Point.fromBoolean(${tmp(1)});
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testDate() {
+        singleRun { (processingEnv) ->
+            val store = TypeAdapterStore.create(Context(processingEnv),
+                    dateTypeConverters(processingEnv))
+            val tDate = processingEnv.elementUtils.getTypeElement("java.util.Date").asType()
+            val adapter = store.findCursorValueReader(tDate, SQLTypeAffinity.INTEGER)
+            assertThat(adapter, notNullValue())
+            assertThat(adapter?.typeMirror(), `is`(tDate))
+            val bindScope = testCodeGenScope()
+            adapter!!.readFromCursor("outDate", "curs", "0", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                final java.lang.Long _tmp;
+                if (curs.isNull(0)) {
+                  _tmp = null;
+                } else {
+                  _tmp = curs.getLong(0);
+                }
+                // convert Long to Date;
+            """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testIntList() {
+        singleRun { invocation ->
+            val binders = createIntListToStringBinders(invocation)
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0],
+                    binders[1])
+
+            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
+            assertThat(adapter, notNullValue())
+
+            val bindScope = testCodeGenScope()
+            adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope)
+            assertThat(bindScope.generate().trim(), `is`("""
+                final java.lang.String ${tmp(0)};
+                ${tmp(0)} = androidx.room.util.StringUtil.joinIntoString(fooVar);
+                if (${tmp(0)} == null) {
+                  stmt.bindNull(41);
+                } else {
+                  stmt.bindString(41, ${tmp(0)});
+                }
+                """.trimIndent()))
+
+            val converter = store.findTypeConverter(binders[0].from,
+                    invocation.context.COMMON_TYPES.STRING)
+            assertThat(converter, notNullValue())
+            assertThat(store.reverse(converter!!), `is`(binders[1]))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testOneWayConversion() {
+        singleRun { invocation ->
+            val binders = createIntListToStringBinders(invocation)
+            val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0])
+            val adapter = store.findColumnTypeAdapter(binders[0].from, null)
+            assertThat(adapter, nullValue())
+
+            val stmtBinder = store.findStatementValueBinder(binders[0].from, null)
+            assertThat(stmtBinder, notNullValue())
+
+            val converter = store.findTypeConverter(binders[0].from,
+                    invocation.context.COMMON_TYPES.STRING)
+            assertThat(converter, notNullValue())
+            assertThat(store.reverse(converter!!), nullValue())
+        }
+    }
+
+    @Test
+    fun testMissingRxRoom() {
+        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE)) { invocation ->
+            val publisherElement = invocation.processingEnv.elementUtils
+                    .getTypeElement(ReactiveStreamsTypeNames.PUBLISHER.toString())
+            assertThat(publisherElement, notNullValue())
+            assertThat(FlowableQueryResultBinderProvider(invocation.context).matches(
+                    MoreTypes.asDeclared(publisherElement.asType())), `is`(true))
+        }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT)
+    }
+
+    @Test
+    fun testFindPublisher() {
+        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE, COMMON.RX2_ROOM)) {
+            invocation ->
+            val publisher = invocation.processingEnv.elementUtils
+                    .getTypeElement(ReactiveStreamsTypeNames.PUBLISHER.toString())
+            assertThat(publisher, notNullValue())
+            assertThat(FlowableQueryResultBinderProvider(invocation.context).matches(
+                    MoreTypes.asDeclared(publisher.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testFindFlowable() {
+        simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, COMMON.FLOWABLE, COMMON.RX2_ROOM)) {
+            invocation ->
+            val flowable = invocation.processingEnv.elementUtils
+                    .getTypeElement(RxJava2TypeNames.FLOWABLE.toString())
+            assertThat(flowable, notNullValue())
+            assertThat(FlowableQueryResultBinderProvider(invocation.context).matches(
+                    MoreTypes.asDeclared(flowable.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testFindLiveData() {
+        simpleRun(jfos = *arrayOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA)) {
+            invocation ->
+            val liveData = invocation.processingEnv.elementUtils
+                    .getTypeElement(LifecyclesTypeNames.LIVE_DATA.toString())
+            assertThat(liveData, notNullValue())
+            assertThat(LiveDataQueryResultBinderProvider(invocation.context).matches(
+                    MoreTypes.asDeclared(liveData.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun findDataSource() {
+        simpleRun {
+            invocation ->
+            val dataSource = invocation.processingEnv.elementUtils
+                    .getTypeElement(DataSource::class.java.canonicalName)
+            assertThat(dataSource, notNullValue())
+            assertThat(DataSourceQueryResultBinderProvider(invocation.context).matches(
+                    MoreTypes.asDeclared(dataSource.asType())), `is`(true))
+        }.failsToCompile().withErrorContaining(ProcessorErrors.PAGING_SPECIFY_DATA_SOURCE_TYPE)
+    }
+
+    @Test
+    fun findPositionalDataSource() {
+        simpleRun {
+            invocation ->
+            val dataSource = invocation.processingEnv.elementUtils
+                    .getTypeElement(PositionalDataSource::class.java.canonicalName)
+            assertThat(dataSource, notNullValue())
+            assertThat(DataSourceQueryResultBinderProvider(invocation.context).matches(
+                    MoreTypes.asDeclared(dataSource.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun findDataSourceFactory() {
+        simpleRun(jfos = COMMON.DATA_SOURCE_FACTORY) {
+            invocation ->
+            val pagedListProvider = invocation.processingEnv.elementUtils
+                    .getTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY.toString())
+            assertThat(pagedListProvider, notNullValue())
+            assertThat(DataSourceFactoryQueryResultBinderProvider(invocation.context).matches(
+                    MoreTypes.asDeclared(pagedListProvider.asType())), `is`(true))
+        }.compilesWithoutError()
+    }
+
+    private fun createIntListToStringBinders(invocation: TestInvocation): List<TypeConverter> {
+        val intType = invocation.processingEnv.elementUtils
+                .getTypeElement(Integer::class.java.canonicalName)
+                .asType()
+        val listType = invocation.processingEnv.elementUtils
+                .getTypeElement(java.util.List::class.java.canonicalName)
+        val listOfInts = invocation.processingEnv.typeUtils.getDeclaredType(listType, intType)
+
+        val intListConverter = object : TypeConverter(listOfInts,
+                invocation.context.COMMON_TYPES.STRING) {
+            override fun convert(inputVarName: String, outputVarName: String,
+                                 scope: CodeGenScope) {
+                scope.builder().apply {
+                    addStatement("$L = $T.joinIntoString($L)", outputVarName, STRING_UTIL,
+                            inputVarName)
+                }
+            }
+        }
+
+        val stringToIntListConverter = object : TypeConverter(
+                invocation.context.COMMON_TYPES.STRING, listOfInts) {
+            override fun convert(inputVarName: String, outputVarName: String,
+                                 scope: CodeGenScope) {
+                scope.builder().apply {
+                    addStatement("$L = $T.splitToIntList($L)", outputVarName, STRING_UTIL,
+                            inputVarName)
+                }
+            }
+        }
+        return listOf(intListConverter, stringToIntListConverter)
+    }
+
+    fun singleRun(handler: (TestInvocation) -> Unit): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.DummyClass",
+                        """
+                        package foo.bar;
+                        import androidx.room.*;
+                        @Entity
+                        public class DummyClass {}
+                        """
+                ), JavaFileObjects.forSourceString("foo.bar.Point",
+                        """
+                        package foo.bar;
+                        import androidx.room.*;
+                        @Entity
+                        public class Point {
+                            public int x, y;
+                            public Point(int x, int y) {
+                                this.x = x;
+                                this.y = y;
+                            }
+                            public static Point fromBoolean(boolean val) {
+                                return val ? new Point(1, 1) : new Point(0, 0);
+                            }
+                            public static boolean toBoolean(Point point) {
+                                return point.x > 0;
+                            }
+                        }
+                        """
+                )))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Entity::class)
+                        .nextRunHandler { invocation ->
+                            handler(invocation)
+                            true
+                        }
+                        .build())
+    }
+
+    fun pointTypeConverters(env: ProcessingEnvironment): List<TypeConverter> {
+        val tPoint = env.elementUtils.getTypeElement("foo.bar.Point").asType()
+        val tBoolean = env.typeUtils.getPrimitiveType(TypeKind.BOOLEAN)
+        return listOf(
+                object : TypeConverter(tPoint, tBoolean) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("$L = $T.toBoolean($L)", outputVarName, from, inputVarName)
+                        }
+                    }
+                },
+                object : TypeConverter(tBoolean, tPoint) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("$L = $T.fromBoolean($L)", outputVarName, tPoint,
+                                    inputVarName)
+                        }
+                    }
+                }
+        )
+    }
+
+    fun dateTypeConverters(env: ProcessingEnvironment): List<TypeConverter> {
+        val tDate = env.elementUtils.getTypeElement("java.util.Date").asType()
+        val tLong = env.elementUtils.getTypeElement("java.lang.Long").asType()
+        return listOf(
+                object : TypeConverter(tDate, tLong) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("// convert Date to Long")
+                        }
+                    }
+                },
+                object : TypeConverter(tLong, tDate) {
+                    override fun convert(inputVarName: String, outputVarName: String,
+                                         scope: CodeGenScope) {
+                        scope.builder().apply {
+                            addStatement("// convert Long to Date")
+                        }
+                    }
+                }
+        )
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
new file mode 100644
index 0000000..4dfa839
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAssignmentTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 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 androidx.room.solver
+
+import androidx.room.ext.getAllFieldsIncludingPrivateSupers
+import androidx.room.ext.isAssignableWithoutVariance
+import androidx.room.testing.TestInvocation
+import com.google.testing.compile.JavaFileObjects
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import simpleRun
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+
+class TypeAssignmentTest {
+    companion object {
+        private val TEST_OBJECT = JavaFileObjects.forSourceString("foo.bar.MyObject",
+                """
+            package foo.bar;
+            import java.util.Set;
+            import java.util.HashSet;
+            import java.util.Map;
+            class MyObject {
+                String mString;
+                Integer mInteger;
+                Set<MyObject> mSet;
+                Set<? extends MyObject> mVarianceSet;
+                HashSet<MyObject> mHashSet;
+                Map<String, ?> mUnboundedMap;
+                Map<String, String> mStringMap;
+            }
+            """.trimIndent())
+    }
+
+    @Test
+    fun basic() {
+        runTest {
+            val testObject = typeElement("foo.bar.MyObject")
+            val string = testObject.getField(processingEnv, "mString")
+            val integer = testObject.getField(processingEnv, "mInteger")
+            assertThat(typeUtils.isAssignableWithoutVariance(string.asType(),
+                    integer.asType()),
+                    `is`(false))
+        }
+    }
+
+    @Test
+    fun generics() {
+        runTest {
+            val testObject = typeElement("foo.bar.MyObject")
+            val set = testObject.getField(processingEnv, "mSet").asType()
+            val hashSet = testObject.getField(processingEnv, "mHashSet").asType()
+            assertThat(typeUtils.isAssignableWithoutVariance(
+                    from = set,
+                    to = hashSet
+            ), `is`(false))
+            assertThat(typeUtils.isAssignableWithoutVariance(
+                    from = hashSet,
+                    to = set
+            ), `is`(true))
+        }
+    }
+
+    @Test
+    fun variance() {
+        /**
+         *  Set<User> userSet = null;
+         *  Set<? extends User> userSet2 = null;
+         *  userSet = userSet2;  // NOT OK for java but kotlin data classes hit this so we want
+         *                       // to accept it
+         */
+        runTest {
+            val testObject = typeElement("foo.bar.MyObject")
+            val set = testObject.getField(processingEnv, "mSet").asType()
+            val varianceSet = testObject.getField(processingEnv, "mVarianceSet").asType()
+            assertThat(typeUtils.isAssignableWithoutVariance(
+                    from = set,
+                    to = varianceSet
+            ), `is`(true))
+            assertThat(typeUtils.isAssignableWithoutVariance(
+                    from = varianceSet,
+                    to = set
+            ), `is`(true))
+        }
+    }
+
+    @Test
+    fun unboundedVariance() {
+        runTest {
+            val testObject = typeElement("foo.bar.MyObject")
+            val unbounded = testObject.getField(processingEnv, "mUnboundedMap").asType()
+            val objectMap = testObject.getField(processingEnv, "mStringMap").asType()
+            assertThat(typeUtils.isAssignableWithoutVariance(
+                    from = unbounded,
+                    to = objectMap
+            ), `is`(false))
+            assertThat(typeUtils.isAssignableWithoutVariance(
+                    from = objectMap,
+                    to = unbounded
+            ), `is`(true))
+        }
+    }
+
+    private fun TypeElement.getField(
+            env: ProcessingEnvironment,
+            name: String): VariableElement {
+        return getAllFieldsIncludingPrivateSupers(env).first {
+            it.simpleName.toString() == name
+        }
+    }
+
+    private fun runTest(handler: TestInvocation.() -> Unit) {
+        simpleRun(TEST_OBJECT) {
+            it.apply { handler() }
+        }.compilesWithoutError()
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
new file mode 100644
index 0000000..4f37cfa
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.solver.query
+
+import androidx.room.Dao
+import androidx.room.Query
+import androidx.room.ext.RoomTypeNames.ROOM_SQL_QUERY
+import androidx.room.ext.RoomTypeNames.STRING_UTIL
+import androidx.room.processor.QueryMethodProcessor
+import androidx.room.testing.TestProcessor
+import androidx.room.writer.QueryWriter
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import testCodeGenScope
+
+@RunWith(JUnit4::class)
+class QueryWriterTest {
+    companion object {
+        const val DAO_PREFIX = """
+                package foo.bar;
+                import androidx.room.*;
+                import java.util.*;
+                @Dao
+                abstract class MyClass {
+                """
+        const val DAO_SUFFIX = "}"
+        val QUERY = ROOM_SQL_QUERY.toString()
+    }
+
+    @Test
+    fun simpleNoArgQuery() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users")
+                abstract java.util.List<Integer> selectAllIds();
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    final java.lang.String _sql = "SELECT id FROM users";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 0);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun simpleStringArgs() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE name LIKE :name")
+                abstract java.util.List<Integer> selectAllIds(String name);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    final java.lang.String _sql = "SELECT id FROM users WHERE name LIKE ?";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 1);
+                    int _argIndex = 1;
+                    if (name == null) {
+                      _stmt.bindNull(_argIndex);
+                    } else {
+                      _stmt.bindString(_argIndex, name);
+                    }
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun twoIntArgs() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:id1,:id2)")
+                abstract java.util.List<Integer> selectAllIds(int id1, int id2);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    final java.lang.String _sql = "SELECT id FROM users WHERE id IN(?,?)";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 2);
+                    int _argIndex = 1;
+                    _stmt.bindLong(_argIndex, id1);
+                    _argIndex = 2;
+                    _stmt.bindLong(_argIndex, id2);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun aLongAndIntVarArg() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
+                abstract java.util.List<Integer> selectAllIds(long time, int... ids);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(
+                    """
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE id IN(");
+                    final int _inputSize = ids.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(") AND age > ");
+                    _stringBuilder.append("?");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 1 + _inputSize;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    for (int _item : ids) {
+                      _stmt.bindLong(_argIndex, _item);
+                      _argIndex ++;
+                    }
+                    _argIndex = 1 + _inputSize;
+                    _stmt.bindLong(_argIndex, time);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    val collectionOut = """
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE id IN(");
+                    final int _inputSize = ids.size();
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(") AND age > ");
+                    _stringBuilder.append("?");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 1 + _inputSize;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    for (java.lang.Integer _item : ids) {
+                      if (_item == null) {
+                        _stmt.bindNull(_argIndex);
+                      } else {
+                        _stmt.bindLong(_argIndex, _item);
+                      }
+                      _argIndex ++;
+                    }
+                    _argIndex = 1 + _inputSize;
+                    _stmt.bindLong(_argIndex, time);
+                    """.trimIndent()
+
+    @Test
+    fun aLongAndIntegerList() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
+                abstract List<Integer> selectAllIds(long time, List<Integer> ids);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(collectionOut))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun aLongAndIntegerSet() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
+                abstract List<Integer> selectAllIds(long time, Set<Integer> ids);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`(collectionOut))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMultipleBindParamsWithSameName() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE age > :age OR bage > :age")
+                abstract List<Integer> selectAllIds(int age);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    final java.lang.String _sql = "SELECT id FROM users WHERE age > ? OR bage > ?";
+                    final $QUERY _stmt = $QUERY.acquire(_sql, 2);
+                    int _argIndex = 1;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 2;
+                    _stmt.bindLong(_argIndex, age);
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMultipleBindParamsWithSameNameWithVarArg() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE age > :age OR bage > :age OR fage IN(:ages)")
+                abstract List<Integer> selectAllIds(int age, int... ages);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE age > ");
+                    _stringBuilder.append("?");
+                    _stringBuilder.append(" OR bage > ");
+                    _stringBuilder.append("?");
+                    _stringBuilder.append(" OR fage IN(");
+                    final int _inputSize = ages.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(")");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 2 + _inputSize;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 2;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 3;
+                    for (int _item : ages) {
+                      _stmt.bindLong(_argIndex, _item);
+                      _argIndex ++;
+                    }
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testMultipleBindParamsWithSameNameWithVarArgInTwoBindings() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE age IN (:ages) OR bage > :age OR fage IN(:ages)")
+                abstract List<Integer> selectAllIds(int age, int... ages);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().trim(), `is`("""
+                    java.lang.StringBuilder _stringBuilder = $STRING_UTIL.newStringBuilder();
+                    _stringBuilder.append("SELECT id FROM users WHERE age IN (");
+                    final int _inputSize = ages.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize);
+                    _stringBuilder.append(") OR bage > ");
+                    _stringBuilder.append("?");
+                    _stringBuilder.append(" OR fage IN(");
+                    final int _inputSize_1 = ages.length;
+                    $STRING_UTIL.appendPlaceholders(_stringBuilder, _inputSize_1);
+                    _stringBuilder.append(")");
+                    final java.lang.String _sql = _stringBuilder.toString();
+                    final int _argCount = 1 + _inputSize + _inputSize_1;
+                    final $QUERY _stmt = $QUERY.acquire(_sql, _argCount);
+                    int _argIndex = 1;
+                    for (int _item : ages) {
+                      _stmt.bindLong(_argIndex, _item);
+                      _argIndex ++;
+                    }
+                    _argIndex = 1 + _inputSize;
+                    _stmt.bindLong(_argIndex, age);
+                    _argIndex = 2 + _inputSize;
+                    for (int _item_1 : ages) {
+                      _stmt.bindLong(_argIndex, _item_1);
+                      _argIndex ++;
+                    }
+                    """.trimIndent()))
+        }.compilesWithoutError()
+    }
+
+    fun singleQueryMethod(vararg input: String,
+                          handler: (QueryWriter) -> Unit):
+            CompileTester {
+        return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
+                .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
+                ))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(Query::class, Dao::class)
+                        .nextRunHandler { invocation ->
+                            val (owner, methods) = invocation.roundEnv
+                                    .getElementsAnnotatedWith(Dao::class.java)
+                                    .map {
+                                        Pair(it,
+                                                invocation.processingEnv.elementUtils
+                                                        .getAllMembers(MoreElements.asType(it))
+                                                        .filter {
+                                                            MoreElements.isAnnotationPresent(it,
+                                                                    Query::class.java)
+                                                        }
+                                        )
+                                    }.filter { it.second.isNotEmpty() }.first()
+                            val parser = QueryMethodProcessor(
+                                    baseContext = invocation.context,
+                                    containing = MoreTypes.asDeclared(owner.asType()),
+                                    executableElement = MoreElements.asExecutable(methods.first()))
+                            val parsedQuery = parser.process()
+                            handler(QueryWriter(parsedQuery))
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
new file mode 100644
index 0000000..d9904cb
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing
+
+import androidx.room.Query
+import com.google.common.truth.Truth
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.atomic.AtomicBoolean
+
+@RunWith(JUnit4::class)
+class InProcessorTest {
+    @Test
+    fun testInProcessorTestRuns() {
+        val didRun = AtomicBoolean(false)
+        Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
+                .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
+                        """
+                        package foo.bar;
+                        abstract public class MyClass {
+                        @androidx.room.Query("foo")
+                        abstract public void setFoo(String foo);
+                        }
+                        """))
+                .processedWith(TestProcessor.builder()
+                        .nextRunHandler { invocation ->
+                            didRun.set(true)
+                            assertThat(invocation.annotations.size, `is`(1))
+                            true
+                        }
+                        .forAnnotations(Query::class)
+                        .build())
+                .compilesWithoutError()
+        assertThat(didRun.get(), `is`(true))
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/TestInvocation.kt b/room/compiler/src/test/kotlin/androidx/room/testing/TestInvocation.kt
new file mode 100644
index 0000000..9aff219
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/TestInvocation.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing
+
+import androidx.room.processor.Context
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.TypeElement
+
+data class TestInvocation(val processingEnv: ProcessingEnvironment,
+                          val annotations: MutableSet<out TypeElement>,
+                          val roundEnv: RoundEnvironment) {
+    val context = Context(processingEnv)
+
+    fun typeElement(qName: String): TypeElement {
+        return processingEnv.elementUtils.getTypeElement(qName)
+    }
+
+    val typeUtils by lazy { processingEnv.typeUtils }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/TestProcessor.kt b/room/compiler/src/test/kotlin/androidx/room/testing/TestProcessor.kt
new file mode 100644
index 0000000..2ec6294
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/TestProcessor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing
+
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.annotation.processing.SupportedSourceVersion
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+
+@SupportedSourceVersion(SourceVersion.RELEASE_8)// test are compiled w/ J_8
+class TestProcessor(val handlers: List<(TestInvocation) -> Boolean>,
+                    val annotations: MutableSet<String>) : AbstractProcessor() {
+    var count = 0
+    override fun process(
+            annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
+        return handlers.getOrNull(count++)?.invoke(
+                    TestInvocation(processingEnv, annotations, roundEnv)) ?: true
+    }
+
+    override fun getSupportedAnnotationTypes(): MutableSet<String> {
+        return annotations
+    }
+
+    class Builder {
+        private var handlers = arrayListOf<(TestInvocation) -> Boolean>()
+        private var annotations = mutableSetOf<String>()
+        fun nextRunHandler(f: (TestInvocation) -> Boolean): Builder {
+            handlers.add(f)
+            return this
+        }
+
+        fun forAnnotations(vararg klasses: KClass<*>): Builder {
+            annotations.addAll(klasses.map { it.java.canonicalName })
+            return this
+        }
+
+        fun build(): TestProcessor {
+            if (annotations.isEmpty()) {
+                throw IllegalStateException("must provide at least 1 annotation")
+            }
+            if (handlers.isEmpty()) {
+                throw IllegalStateException("must provide at least 1 handler")
+            }
+            return TestProcessor(handlers, annotations)
+        }
+    }
+
+    companion object {
+        fun builder(): Builder = Builder()
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
new file mode 100644
index 0000000..345f94a
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.room.ColumnInfo
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.Relation
+import androidx.room.ext.LifecyclesTypeNames
+import androidx.room.ext.PagingTypeNames
+import androidx.room.ext.ReactiveStreamsTypeNames
+import androidx.room.ext.RoomRxJava2TypeNames
+import androidx.room.ext.RxJava2TypeNames
+import androidx.room.processor.EntityProcessor
+import androidx.room.solver.CodeGenScope
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.verifier.DatabaseVerifier
+import androidx.room.writer.ClassWriter
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.ClassName
+import org.mockito.Mockito
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.io.File
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.tools.JavaFileObject
+
+object COMMON {
+    val USER by lazy {
+        loadJavaCode("common/input/User.java", "foo.bar.User")
+    }
+    val USER_TYPE_NAME by lazy {
+        ClassName.get("foo.bar", "User")
+    }
+    val BOOK by lazy {
+        loadJavaCode("common/input/Book.java", "foo.bar.Book")
+    }
+    val NOT_AN_ENTITY by lazy {
+        loadJavaCode("common/input/NotAnEntity.java", "foo.bar.NotAnEntity")
+    }
+
+    val NOT_AN_ENTITY_TYPE_NAME by lazy {
+        ClassName.get("foo.bar", "NotAnEntity")
+    }
+
+    val MULTI_PKEY_ENTITY by lazy {
+        loadJavaCode("common/input/MultiPKeyEntity.java", "MultiPKeyEntity")
+    }
+    val LIVE_DATA by lazy {
+        loadJavaCode("common/input/LiveData.java", LifecyclesTypeNames.LIVE_DATA.toString())
+    }
+    val COMPUTABLE_LIVE_DATA by lazy {
+        loadJavaCode("common/input/ComputableLiveData.java",
+                LifecyclesTypeNames.COMPUTABLE_LIVE_DATA.toString())
+    }
+    val PUBLISHER by lazy {
+        loadJavaCode("common/input/reactivestreams/Publisher.java",
+                ReactiveStreamsTypeNames.PUBLISHER.toString())
+    }
+    val FLOWABLE by lazy {
+        loadJavaCode("common/input/rxjava2/Flowable.java", RxJava2TypeNames.FLOWABLE.toString())
+    }
+
+    val RX2_ROOM by lazy {
+        loadJavaCode("common/input/Rx2Room.java", RoomRxJava2TypeNames.RX_ROOM.toString())
+    }
+
+    val DATA_SOURCE_FACTORY by lazy {
+        loadJavaCode("common/input/DataSource.java", "androidx.paging.DataSource")
+    }
+
+    val POSITIONAL_DATA_SOURCE by lazy {
+        loadJavaCode("common/input/PositionalDataSource.java",
+                PagingTypeNames.POSITIONAL_DATA_SOURCE.toString())
+    }
+}
+fun testCodeGenScope(): CodeGenScope {
+    return CodeGenScope(Mockito.mock(ClassWriter::class.java))
+}
+
+fun simpleRun(vararg jfos: JavaFileObject, f: (TestInvocation) -> Unit): CompileTester {
+    return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+            .that(jfos.toList() + JavaFileObjects.forSourceString("foo.bar.MyClass",
+                    """
+                    package foo.bar;
+                    abstract public class MyClass {
+                    @androidx.room.Query("foo")
+                    abstract public void setFoo(String foo);
+                    }
+                    """))
+            .processedWith(TestProcessor.builder()
+                    .nextRunHandler {
+                        f(it)
+                        true
+                    }
+                    .forAnnotations(Query::class, PrimaryKey::class, Embedded::class,
+                            ColumnInfo::class, Relation::class, Entity::class)
+                    .build())
+}
+
+fun loadJavaCode(fileName: String, qName: String): JavaFileObject {
+    val contents = File("src/test/data/$fileName").readText(Charsets.UTF_8)
+    return JavaFileObjects.forSourceString(qName, contents)
+}
+
+fun createVerifierFromEntities(invocation: TestInvocation): DatabaseVerifier {
+    val entities = invocation.roundEnv.getElementsAnnotatedWith(Entity::class.java).map {
+        EntityProcessor(invocation.context, MoreElements.asType(it)).process()
+    }
+    return DatabaseVerifier.create(invocation.context, Mockito.mock(Element::class.java),
+            entities)!!
+}
+
+/**
+ * Create mocks of [Element] and [TypeMirror] so that they can be used for instantiating a fake
+ * [androidx.room.vo.Field].
+ */
+fun mockElementAndType(): Pair<Element, TypeMirror> {
+    val element = mock(Element::class.java)
+    val type = mock(TypeMirror::class.java)
+    doReturn(TypeKind.DECLARED).`when`(type).kind
+    doReturn(type).`when`(element).asType()
+    return element to type
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt b/room/compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
new file mode 100644
index 0000000..d72d52d
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
@@ -0,0 +1,273 @@
+/*
+ * 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 androidx.room.verifier
+
+import androidx.room.parser.Collate
+import androidx.room.parser.SQLTypeAffinity
+import androidx.room.processor.Context
+import androidx.room.testing.TestInvocation
+import androidx.room.vo.CallType
+import androidx.room.vo.Constructor
+import androidx.room.vo.Database
+import androidx.room.vo.Entity
+import androidx.room.vo.Field
+import androidx.room.vo.FieldGetter
+import androidx.room.vo.FieldSetter
+import androidx.room.vo.PrimaryKey
+import collect
+import columnNames
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.hasItem
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import simpleRun
+import java.sql.Connection
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+@RunWith(Parameterized::class)
+class DatabaseVerifierTest(private val useLocalizedCollation: Boolean) {
+    @Test
+    fun testSimpleDatabase() {
+        simpleRun { invocation ->
+            val verifier = createVerifier(invocation)
+            val stmt = verifier.connection.createStatement()
+            val rs = stmt.executeQuery("select * from sqlite_master WHERE type='table'")
+            assertThat(
+                    rs.collect { set -> set.getString("name") }, hasItem(`is`("User")))
+            val table = verifier.connection.prepareStatement("select * from User")
+            assertThat(table.columnNames(), `is`(listOf("id", "name", "lastName", "ratio")))
+
+            assertThat(getPrimaryKeys(verifier.connection, "User"), `is`(listOf("id")))
+        }.compilesWithoutError()
+    }
+
+    private fun createVerifier(invocation: TestInvocation): DatabaseVerifier {
+        return DatabaseVerifier.create(invocation.context, mock(Element::class.java),
+                userDb(invocation.context).entities)!!
+    }
+
+    @Test
+    fun testFullEntityQuery() {
+        validQueryTest("select * from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("name", SQLTypeAffinity.TEXT),
+                            ColumnInfo("lastName", SQLTypeAffinity.TEXT),
+                            ColumnInfo("ratio", SQLTypeAffinity.REAL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testPartialFields() {
+        validQueryTest("select id, lastName from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("lastName", SQLTypeAffinity.TEXT)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testRenamedField() {
+        validQueryTest("select id as myId, lastName from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            ColumnInfo("myId", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("lastName", SQLTypeAffinity.TEXT)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testGrouped() {
+        validQueryTest("select MAX(ratio) from User GROUP BY name") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            // unfortunately, we don't get this information
+                            ColumnInfo("MAX(ratio)", SQLTypeAffinity.NULL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testConcat() {
+        validQueryTest("select name || lastName as mergedName from User") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            // unfortunately, we don't get this information
+                            ColumnInfo("mergedName", SQLTypeAffinity.NULL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testResultWithArgs() {
+        validQueryTest("select id, name || lastName as mergedName from User where name LIKE ?") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            // unfortunately, we don't get this information
+                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("mergedName", SQLTypeAffinity.NULL)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testDeleteQuery() {
+        validQueryTest("delete from User where name LIKE ?") {
+            assertThat(it, `is`(QueryResultInfo(emptyList())))
+        }
+    }
+
+    @Test
+    fun testUpdateQuery() {
+        validQueryTest("update User set name = ? WHERE id = ?") {
+            assertThat(it, `is`(QueryResultInfo(emptyList())))
+        }
+    }
+
+    @Test
+    fun testBadQuery() {
+        simpleRun { invocation ->
+            val verifier = createVerifier(invocation)
+            val (_, error) = verifier.analyze("select foo from User")
+            assertThat(error, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun testCollate() {
+        validQueryTest("SELECT id, name FROM user ORDER BY name COLLATE LOCALIZED ASC") {
+            assertThat(it, `is`(
+                    QueryResultInfo(listOf(
+                            // unfortunately, we don't get this information
+                            ColumnInfo("id", SQLTypeAffinity.INTEGER),
+                            ColumnInfo("name", SQLTypeAffinity.TEXT)
+                    ))))
+        }
+    }
+
+    @Test
+    fun testCollateBasQuery() {
+        simpleRun { invocation ->
+            val verifier = createVerifier(invocation)
+            val (_, error) = verifier.analyze(
+                    "SELECT id, name FROM user ORDER BY name COLLATE LOCALIZEDASC")
+            assertThat(error, notNullValue())
+        }.compilesWithoutError()
+    }
+
+    private fun validQueryTest(sql: String, cb: (QueryResultInfo) -> Unit) {
+        simpleRun { invocation ->
+            val verifier = createVerifier(invocation)
+            val info = verifier.analyze(sql)
+            cb(info)
+        }.compilesWithoutError()
+    }
+
+    private fun userDb(context: Context): Database {
+        return database(entity("User",
+                field("id", primitive(context, TypeKind.INT), SQLTypeAffinity.INTEGER),
+                field("name", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+                field("lastName", context.COMMON_TYPES.STRING, SQLTypeAffinity.TEXT),
+                field("ratio", primitive(context, TypeKind.FLOAT), SQLTypeAffinity.REAL)))
+    }
+
+    private fun database(vararg entities: Entity): Database {
+        return Database(
+                element = mock(TypeElement::class.java),
+                type = mock(TypeMirror::class.java),
+                entities = entities.toList(),
+                daoMethods = emptyList(),
+                version = -1,
+                exportSchema = false,
+                enableForeignKeys = false)
+    }
+
+    private fun entity(tableName: String, vararg fields: Field): Entity {
+        return Entity(
+                element = mock(TypeElement::class.java),
+                tableName = tableName,
+                type = mock(DeclaredType::class.java),
+                fields = fields.toList(),
+                embeddedFields = emptyList(),
+                indices = emptyList(),
+                primaryKey = PrimaryKey(null, fields.take(1), false),
+                foreignKeys = emptyList(),
+                constructor = Constructor(mock(ExecutableElement::class.java), emptyList())
+        )
+    }
+
+    private fun field(name: String, type: TypeMirror, affinity: SQLTypeAffinity): Field {
+        val element = mock(Element::class.java)
+        doReturn(type).`when`(element).asType()
+        val f = Field(
+                element = element,
+                name = name,
+                type = type,
+                columnName = name,
+                affinity = affinity,
+                collate = if (useLocalizedCollation && affinity == SQLTypeAffinity.TEXT) {
+                    Collate.LOCALIZED
+                } else {
+                    null
+                }
+        )
+        assignGetterSetter(f, name, type)
+        return f
+    }
+
+    private fun assignGetterSetter(f: Field, name: String, type: TypeMirror) {
+        f.getter = FieldGetter(name, type, CallType.FIELD)
+        f.setter = FieldSetter(name, type, CallType.FIELD)
+    }
+
+    private fun primitive(context: Context, kind: TypeKind): PrimitiveType {
+        return context.processingEnv.typeUtils.getPrimitiveType(kind)
+    }
+
+    private fun getPrimaryKeys(connection: Connection, tableName: String): List<String> {
+        val stmt = connection.createStatement()
+        val resultSet = stmt.executeQuery("PRAGMA table_info($tableName)")
+        return resultSet.collect {
+            Pair(it.getString("name"), it.getInt("pk"))
+        }
+                .filter { it.second > 0 }
+                .sortedBy { it.second }
+                .map { it.first }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "useLocalizedCollation={0}")
+        @JvmStatic
+        fun params() = arrayListOf(true, false)
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/vo/EntityTest.kt b/room/compiler/src/test/kotlin/androidx/room/vo/EntityTest.kt
new file mode 100644
index 0000000..627e659
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/vo/EntityTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2018 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 androidx.room.vo
+
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+
+@RunWith(JUnit4::class)
+class EntityTest {
+
+    @Test
+    fun shouldBeDeletedAfter() {
+        val child = createEntity("Child", listOf(
+                createForeignKey("NoAction", ForeignKeyAction.NO_ACTION, false),
+                createForeignKey("NoActionDeferred", ForeignKeyAction.NO_ACTION, true),
+                createForeignKey("Restrict", ForeignKeyAction.RESTRICT, false),
+                createForeignKey("RestrictDeferred", ForeignKeyAction.RESTRICT, true),
+                createForeignKey("SetNull", ForeignKeyAction.SET_NULL, false),
+                createForeignKey("SetNullDeferred", ForeignKeyAction.SET_NULL, true),
+                createForeignKey("SetDefault", ForeignKeyAction.SET_DEFAULT, false),
+                createForeignKey("SetDefaultDeferred", ForeignKeyAction.SET_DEFAULT, true),
+                createForeignKey("Cascade", ForeignKeyAction.CASCADE, false),
+                createForeignKey("CascadeDeferred", ForeignKeyAction.CASCADE, true)))
+        val noAction = createEntity("NoAction")
+        val noActionDeferred = createEntity("NoActionDeferred")
+        val restrict = createEntity("Restrict")
+        val restrictDeferred = createEntity("RestrictDeferred")
+        val setNull = createEntity("SetNull")
+        val setNullDeferred = createEntity("SetNullDeferred")
+        val setDefault = createEntity("SetDefault")
+        val setDefaultDeferred = createEntity("SetDefaultDeferred")
+        val cascade = createEntity("Cascade")
+        val cascadeDeferred = createEntity("CascadeDeferred")
+        val irrelevant = createEntity("Irrelevant")
+        assertThat(child.shouldBeDeletedAfter(noAction), `is`(true))
+        assertThat(child.shouldBeDeletedAfter(noActionDeferred), `is`(false))
+        assertThat(child.shouldBeDeletedAfter(restrict), `is`(true))
+        assertThat(child.shouldBeDeletedAfter(restrictDeferred), `is`(true))
+        assertThat(child.shouldBeDeletedAfter(setNull), `is`(false))
+        assertThat(child.shouldBeDeletedAfter(setNullDeferred), `is`(false))
+        assertThat(child.shouldBeDeletedAfter(setDefault), `is`(false))
+        assertThat(child.shouldBeDeletedAfter(setDefaultDeferred), `is`(false))
+        assertThat(child.shouldBeDeletedAfter(cascade), `is`(false))
+        assertThat(child.shouldBeDeletedAfter(cascadeDeferred), `is`(false))
+        assertThat(child.shouldBeDeletedAfter(irrelevant), `is`(false))
+    }
+
+    private fun createEntity(
+            tableName: String,
+            foreignKeys: List<ForeignKey> = emptyList()): Entity {
+        return Entity(
+                element = mock(TypeElement::class.java),
+                tableName = tableName,
+                type = mock(DeclaredType::class.java),
+                fields = emptyList(),
+                embeddedFields = emptyList(),
+                primaryKey = PrimaryKey(mock(Element::class.java), emptyList(), false),
+                indices = emptyList(),
+                foreignKeys = foreignKeys,
+                constructor = Constructor(mock(ExecutableElement::class.java), emptyList()))
+    }
+
+    private fun createForeignKey(
+            parentTable: String,
+            onDelete: ForeignKeyAction,
+            deferred: Boolean): ForeignKey {
+        return ForeignKey(
+                parentTable = parentTable,
+                parentColumns = emptyList(),
+                childFields = emptyList(),
+                onDelete = onDelete,
+                onUpdate = ForeignKeyAction.NO_ACTION,
+                deferred = deferred)
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/vo/IndexTest.kt b/room/compiler/src/test/kotlin/androidx/room/vo/IndexTest.kt
new file mode 100644
index 0000000..e7150ea
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/vo/IndexTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.room.vo
+
+import androidx.room.parser.SQLTypeAffinity
+import mockElementAndType
+import org.hamcrest.CoreMatchers
+import org.hamcrest.MatcherAssert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class IndexTest {
+    @Test
+    fun createSimpleSQL() {
+        val index = Index("foo", false, listOf(mockField("bar"), mockField("baz")))
+        MatcherAssert.assertThat(index.createQuery("my_table"), CoreMatchers.`is`(
+                "CREATE  INDEX `foo` ON `my_table` (`bar`, `baz`)"
+        ))
+    }
+
+    @Test
+    fun createUnique() {
+        val index = Index("foo", true, listOf(mockField("bar"), mockField("baz")))
+        MatcherAssert.assertThat(index.createQuery("my_table"), CoreMatchers.`is`(
+                "CREATE UNIQUE INDEX `foo` ON `my_table` (`bar`, `baz`)"
+        ))
+    }
+
+    private fun mockField(columnName: String): Field {
+        val (element, type) = mockElementAndType()
+        return Field(
+                element = element,
+                name = columnName + "_field",
+                affinity = SQLTypeAffinity.TEXT,
+                type = type,
+                columnName = columnName
+        )
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
new file mode 100644
index 0000000..5ed7840
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import COMMON
+import androidx.room.ext.RoomTypeNames
+import androidx.room.processor.DaoProcessor
+import androidx.room.testing.TestProcessor
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import createVerifierFromEntities
+import loadJavaCode
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class DaoWriterTest {
+    @Test
+    fun complexDao() {
+        singleDao(
+                loadJavaCode("databasewriter/input/ComplexDatabase.java",
+                        "foo.bar.ComplexDatabase"),
+                loadJavaCode("daoWriter/input/ComplexDao.java", "foo.bar.ComplexDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/ComplexDao.java", "foo.bar.ComplexDao_Impl")
+        )
+    }
+
+    @Test
+    fun writerDao() {
+        singleDao(
+                loadJavaCode("daoWriter/input/WriterDao.java", "foo.bar.WriterDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/WriterDao.java", "foo.bar.WriterDao_Impl")
+        )
+    }
+
+    @Test
+    fun deletionDao() {
+        singleDao(
+                loadJavaCode("daoWriter/input/DeletionDao.java", "foo.bar.DeletionDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/DeletionDao.java", "foo.bar.DeletionDao_Impl")
+        )
+    }
+
+    @Test
+    fun updateDao() {
+        singleDao(
+                loadJavaCode("daoWriter/input/UpdateDao.java", "foo.bar.UpdateDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("daoWriter/output/UpdateDao.java", "foo.bar.UpdateDao_Impl")
+        )
+    }
+
+    fun singleDao(vararg jfo: JavaFileObject): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfo.toList() + COMMON.USER + COMMON.MULTI_PKEY_ENTITY + COMMON.BOOK +
+                        COMMON.LIVE_DATA + COMMON.COMPUTABLE_LIVE_DATA)
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(androidx.room.Dao::class)
+                        .nextRunHandler { invocation ->
+                            val dao = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            androidx.room.Dao::class.java)
+                                    .first()
+                            val db = invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            androidx.room.Database::class.java)
+                                    .firstOrNull()
+                            val dbType = MoreTypes.asDeclared(if (db != null) {
+                                db.asType()
+                            } else {
+                                invocation.context.processingEnv.elementUtils
+                                        .getTypeElement(RoomTypeNames.ROOM_DB.toString()).asType()
+                            })
+                            val parser = DaoProcessor(
+                                    baseContext = invocation.context,
+                                    element = MoreElements.asType(dao),
+                                    dbType = dbType,
+                                    dbVerifier = createVerifierFromEntities(invocation))
+                            val parsedDao = parser.process()
+                            DaoWriter(parsedDao, invocation.processingEnv)
+                                    .write(invocation.processingEnv)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
new file mode 100644
index 0000000..f217e63
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/DatabaseWriterTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import COMMON
+import androidx.room.RoomProcessor
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import loadJavaCode
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class DatabaseWriterTest {
+    @Test
+    fun simpleDb() {
+        singleDb(
+                loadJavaCode("databasewriter/input/ComplexDatabase.java",
+                        "foo.bar.ComplexDatabase"),
+                loadJavaCode("daoWriter/input/ComplexDao.java",
+                        "foo.bar.ComplexDao")
+        ).compilesWithoutError().and().generatesSources(
+                loadJavaCode("databasewriter/output/ComplexDatabase.java",
+                        "foo.bar.ComplexDatabase_Impl")
+        )
+    }
+
+    private fun singleDb(vararg jfo: JavaFileObject): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(jfo.toList() + COMMON.USER + COMMON.LIVE_DATA + COMMON.COMPUTABLE_LIVE_DATA)
+                .processedWith(RoomProcessor())
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/EntityCursorConverterWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/EntityCursorConverterWriterTest.kt
new file mode 100644
index 0000000..525cfe3
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/EntityCursorConverterWriterTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.room.processor.BaseEntityParserTest
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeSpec
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.lang.model.element.Modifier
+
+@RunWith(JUnit4::class)
+class EntityCursorConverterWriterTest : BaseEntityParserTest() {
+    companion object {
+        val OUT_PREFIX = """
+            package foo.bar;
+            import android.database.Cursor;
+            import java.lang.SuppressWarnings;
+            import javax.annotation.Generated;
+            @Generated("androidx.room.RoomProcessor")
+            @SuppressWarnings("unchecked")
+            public class MyContainerClass {
+            """.trimIndent()
+        const val OUT_SUFFIX = "}"
+    }
+
+    @Test
+    fun generateSimple() {
+        generateAndMatch(
+                """
+                @PrimaryKey
+                private int id;
+                String name;
+                String lastName;
+                int age;
+                public int getId() { return id; }
+                public void setId(int id) { this.id = id; }
+                """,
+                """
+                private MyEntity __entityCursorConverter_fooBarMyEntity(Cursor cursor) {
+                  final MyEntity _entity;
+                  final int _cursorIndexOfId = cursor.getColumnIndex("id");
+                  final int _cursorIndexOfName = cursor.getColumnIndex("name");
+                  final int _cursorIndexOfLastName = cursor.getColumnIndex("lastName");
+                  final int _cursorIndexOfAge = cursor.getColumnIndex("age");
+                  _entity = new MyEntity();
+                  if (_cursorIndexOfId != -1) {
+                    final int _tmpId;
+                    _tmpId = cursor.getInt(_cursorIndexOfId);
+                    _entity.setId(_tmpId);
+                  }
+                  if (_cursorIndexOfName != -1) {
+                    _entity.name = cursor.getString(_cursorIndexOfName);
+                  }
+                  if (_cursorIndexOfLastName != -1) {
+                    _entity.lastName = cursor.getString(_cursorIndexOfLastName);
+                  }
+                  if (_cursorIndexOfAge != -1) {
+                    _entity.age = cursor.getInt(_cursorIndexOfAge);
+                  }
+                  return _entity;
+                }
+                """.trimIndent())
+    }
+
+    fun generateAndMatch(input: String, output: String,
+                         attributes: Map<String, String> = mapOf()) {
+        generate(input, attributes)
+                .compilesWithoutError()
+                .and()
+                .generatesSources(JavaFileObjects.forSourceString(
+                        "foo.bar.MyEntity_CursorConverter",
+                        listOf(OUT_PREFIX, output, OUT_SUFFIX).joinToString("\n")))
+    }
+
+    fun generate(input: String, attributes: Map<String, String> = mapOf()): CompileTester {
+        return singleEntity(input, attributes) { entity, invocation ->
+            val className = ClassName.get("foo.bar", "MyContainerClass")
+            val writer = object : ClassWriter(className) {
+                override fun createTypeSpecBuilder(): TypeSpec.Builder {
+                    getOrCreateMethod(EntityCursorConverterWriter(entity))
+                    return TypeSpec.classBuilder(className).apply {
+                        addModifiers(Modifier.PUBLIC)
+                    }
+                }
+            }
+            writer.write(invocation.processingEnv)
+        }
+    }
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/SQLiteOpenHelperWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/SQLiteOpenHelperWriterTest.kt
new file mode 100644
index 0000000..0ad0efc
--- /dev/null
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/SQLiteOpenHelperWriterTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.writer
+
+import androidx.annotation.NonNull
+import androidx.room.processor.DatabaseProcessor
+import androidx.room.testing.TestInvocation
+import androidx.room.testing.TestProcessor
+import androidx.room.vo.Database
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SQLiteOpenHelperWriterTest {
+    companion object {
+        const val ENTITY_PREFIX = """
+            package foo.bar;
+            import androidx.annotation.NonNull;
+            import androidx.room.*;
+            @Entity%s
+            public class MyEntity {
+            """
+        const val ENTITY_SUFFIX = "}"
+        const val DATABASE_CODE = """
+            package foo.bar;
+            import androidx.room.*;
+            @Database(entities = {MyEntity.class}, version = 3)
+            abstract public class MyDatabase extends RoomDatabase {
+            }
+            """
+    }
+
+    @Test
+    fun createSimpleEntity() {
+        singleEntity(
+                """
+                @PrimaryKey
+                @NonNull
+                String uuid;
+                String name;
+                int age;
+                """.trimIndent()
+        ) { database, _ ->
+            val query = SQLiteOpenHelperWriter(database)
+                    .createQuery(database.entities.first())
+            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                    " `MyEntity` (`uuid` TEXT NOT NULL, `name` TEXT, `age` INTEGER NOT NULL," +
+                    " PRIMARY KEY(`uuid`))"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun multiplePrimaryKeys() {
+        singleEntity(
+                """
+                @NonNull
+                String uuid;
+                @NonNull
+                String name;
+                int age;
+                """.trimIndent(), attributes = mapOf("primaryKeys" to "{\"uuid\", \"name\"}")
+        ) { database, _ ->
+            val query = SQLiteOpenHelperWriter(database)
+                    .createQuery(database.entities.first())
+            assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                    " `MyEntity` (`uuid` TEXT NOT NULL, `name` TEXT NOT NULL, " +
+                    "`age` INTEGER NOT NULL, PRIMARY KEY(`uuid`, `name`))"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun autoIncrementObject() {
+        listOf("Long", "Integer").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                $type uuid;
+                String name;
+                int age;
+                """.trimIndent()
+            ) { database, _ ->
+                val query = SQLiteOpenHelperWriter(database)
+                        .createQuery(database.entities.first())
+                assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                        " `MyEntity` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT," +
+                        " `name` TEXT, `age` INTEGER NOT NULL)"))
+            }.compilesWithoutError()
+        }
+    }
+
+    @Test
+    fun autoIncrementPrimitives() {
+        listOf("long", "int").forEach { type ->
+            singleEntity(
+                    """
+                @PrimaryKey(autoGenerate = true)
+                $type uuid;
+                String name;
+                int age;
+                """.trimIndent()
+            ) { database, _ ->
+                val query = SQLiteOpenHelperWriter(database)
+                        .createQuery(database.entities.first())
+                assertThat(query, `is`("CREATE TABLE IF NOT EXISTS" +
+                        " `MyEntity` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                        " `name` TEXT, `age` INTEGER NOT NULL)"))
+            }.compilesWithoutError()
+        }
+    }
+
+    fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
+                     handler: (Database, TestInvocation) -> Unit): CompileTester {
+        val attributesReplacement: String
+        if (attributes.isEmpty()) {
+            attributesReplacement = ""
+        } else {
+            attributesReplacement = "(" +
+                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
+                    ")".trimIndent()
+        }
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+                .that(listOf(JavaFileObjects.forSourceString("foo.bar.MyEntity",
+                        ENTITY_PREFIX.format(attributesReplacement) + input + ENTITY_SUFFIX
+                ), JavaFileObjects.forSourceString("foo.bar.MyDatabase",
+                        DATABASE_CODE)))
+                .processedWith(TestProcessor.builder()
+                        .forAnnotations(androidx.room.Database::class,
+                                NonNull::class)
+                        .nextRunHandler { invocation ->
+                            val db = MoreElements.asType(invocation.roundEnv
+                                    .getElementsAnnotatedWith(
+                                            androidx.room.Database::class.java)
+                                    .first())
+                            handler(DatabaseProcessor(invocation.context, db).process(), invocation)
+                            true
+                        }
+                        .build())
+    }
+}
diff --git a/room/guava/build.gradle b/room/guava/build.gradle
index 3837406..3fc535c 100644
--- a/room/guava/build.gradle
+++ b/room/guava/build.gradle
@@ -24,9 +24,9 @@
 }
 
 dependencies {
-    api(project(":room:common"))
-    api(project(":room:runtime"))
-    api(project(":arch:runtime"))
+    api(project(":room:room-common"))
+    api(project(":room:room-runtime"))
+    api(project(":arch:core-runtime"))
     api(SUPPORT_ANNOTATIONS, libs.support_exclude_config)
     api(GUAVA_ANDROID)
 }
diff --git a/room/guava/src/main/AndroidManifest.xml b/room/guava/src/main/AndroidManifest.xml
index 7c5004a..cba56a8 100644
--- a/room/guava/src/main/AndroidManifest.xml
+++ b/room/guava/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.persistence.room.guava">
+          package="androidx.room.guava">
 </manifest>
diff --git a/room/guava/src/main/java/android/arch/persistence/room/guava/GuavaRoom.java b/room/guava/src/main/java/android/arch/persistence/room/guava/GuavaRoom.java
deleted file mode 100644
index 1c9a488..0000000
--- a/room/guava/src/main/java/android/arch/persistence/room/guava/GuavaRoom.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2018 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.guava;
-
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.persistence.room.RoomSQLiteQuery;
-import android.support.annotation.RestrictTo;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListenableFutureTask;
-
-import java.util.concurrent.Callable;
-
-/**
- * A class to hold static methods used by code generation in Room's Guava compatibility library.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@SuppressWarnings("unused") // Used in GuavaListenableFutureQueryResultBinder code generation.
-public class GuavaRoom {
-
-    private GuavaRoom() {}
-
-    /**
-     * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
-     * {@link ArchTaskExecutor}'s background-threaded Executor.
-     */
-    public static <T> ListenableFuture<T> createListenableFuture(
-            final Callable<T> callable,
-            final RoomSQLiteQuery query,
-            final boolean releaseQuery) {
-        ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.create(callable);
-        ArchTaskExecutor.getInstance().executeOnDiskIO(listenableFutureTask);
-
-        if (releaseQuery) {
-            Futures.addCallback(
-                    listenableFutureTask,
-                    new FutureCallback<T>() {
-                        @Override
-                        public void onSuccess(T t) {
-                            query.release();
-                        }
-
-                        @Override
-                        public void onFailure(Throwable throwable) {
-                            query.release();
-                        }
-                    },
-                    directExecutor());
-        }
-
-        return listenableFutureTask;
-    }
-}
diff --git a/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java b/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
new file mode 100644
index 0000000..be483aa
--- /dev/null
+++ b/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 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 androidx.room.guava;
+
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import androidx.annotation.RestrictTo;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.room.RoomSQLiteQuery;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableFutureTask;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A class to hold static methods used by code generation in Room's Guava compatibility library.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressWarnings("unused") // Used in GuavaListenableFutureQueryResultBinder code generation.
+public class GuavaRoom {
+
+    private GuavaRoom() {}
+
+    /**
+     * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
+     * {@link ArchTaskExecutor}'s background-threaded Executor.
+     */
+    public static <T> ListenableFuture<T> createListenableFuture(
+            final Callable<T> callable,
+            final RoomSQLiteQuery query,
+            final boolean releaseQuery) {
+        ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.create(callable);
+        ArchTaskExecutor.getInstance().executeOnDiskIO(listenableFutureTask);
+
+        if (releaseQuery) {
+            Futures.addCallback(
+                    listenableFutureTask,
+                    new FutureCallback<T>() {
+                        @Override
+                        public void onSuccess(T t) {
+                            query.release();
+                        }
+
+                        @Override
+                        public void onFailure(Throwable throwable) {
+                            query.release();
+                        }
+                    },
+                    directExecutor());
+        }
+
+        return listenableFutureTask;
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 162f325..e32db0c 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -39,13 +39,13 @@
 }
 
 dependencies {
-    implementation(project(":room:common"))
-    implementation(project(":persistence:db"))
-    implementation(project(":persistence:db-framework"))
-    implementation(project(":room:runtime"))
-    implementation(project(":arch:runtime"))
-    implementation(project(":lifecycle:extensions"))
-    kaptAndroidTest project(":room:compiler")
+    implementation(project(":room:room-common"))
+    implementation(project(":sqlite:sqlite"))
+    implementation(project(":sqlite:sqlite-framework"))
+    implementation(project(":room:room-runtime"))
+    implementation(project(":arch:core-runtime"))
+    implementation(project(":lifecycle:lifecycle-extensions"))
+    kaptAndroidTest project(":room:room-compiler")
 
     androidTestImplementation(TEST_RUNNER) {
         exclude module: 'support-annotations'
@@ -56,12 +56,12 @@
         exclude module: "hamcrest-core"
     })
     // IJ's gradle integration just cannot figure this out ...
-    androidTestImplementation project(':lifecycle:extensions')
-    androidTestImplementation project(':lifecycle:common')
-    androidTestImplementation project(':lifecycle:runtime')
-    androidTestImplementation project(':room:guava')
-    androidTestImplementation project(':room:testing')
-    androidTestImplementation project(':room:rxjava2')
+    androidTestImplementation project(':lifecycle:lifecycle-extensions')
+    androidTestImplementation project(':lifecycle:lifecycle-common')
+    androidTestImplementation project(':lifecycle:lifecycle-runtime')
+    androidTestImplementation project(':room:room-guava')
+    androidTestImplementation project(':room:room-testing')
+    androidTestImplementation project(':room:room-rxjava2')
     androidTestImplementation project(':arch:core-testing')
     androidTestImplementation(GUAVA_ANDROID)
     androidTestImplementation(RX_JAVA)
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.BooksDatabase/1.json b/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.BooksDatabase/1.json
deleted file mode 100644
index dd056e2..0000000
--- a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.BooksDatabase/1.json
+++ /dev/null
@@ -1,159 +0,0 @@
-{
-  "formatVersion": 1,
-  "database": {
-    "version": 1,
-    "identityHash": "64fa560604c57044726b190dadbd8258",
-    "entities": [
-      {
-        "tableName": "Book",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookId` TEXT NOT NULL, `title` TEXT NOT NULL, `bookPublisherId` TEXT NOT NULL, PRIMARY KEY(`bookId`), FOREIGN KEY(`bookPublisherId`) REFERENCES `Publisher`(`publisherId`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
-        "fields": [
-          {
-            "fieldPath": "bookId",
-            "columnName": "bookId",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "title",
-            "columnName": "title",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "bookPublisherId",
-            "columnName": "bookPublisherId",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "columnNames": [
-            "bookId"
-          ],
-          "autoGenerate": false
-        },
-        "indices": [],
-        "foreignKeys": [
-          {
-            "table": "Publisher",
-            "onDelete": "NO ACTION",
-            "onUpdate": "NO ACTION",
-            "columns": [
-              "bookPublisherId"
-            ],
-            "referencedColumns": [
-              "publisherId"
-            ]
-          }
-        ]
-      },
-      {
-        "tableName": "Author",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`authorId` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`authorId`))",
-        "fields": [
-          {
-            "fieldPath": "authorId",
-            "columnName": "authorId",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "columnNames": [
-            "authorId"
-          ],
-          "autoGenerate": false
-        },
-        "indices": [],
-        "foreignKeys": []
-      },
-      {
-        "tableName": "Publisher",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`publisherId` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`publisherId`))",
-        "fields": [
-          {
-            "fieldPath": "publisherId",
-            "columnName": "publisherId",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "name",
-            "columnName": "name",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "columnNames": [
-            "publisherId"
-          ],
-          "autoGenerate": false
-        },
-        "indices": [],
-        "foreignKeys": []
-      },
-      {
-        "tableName": "BookAuthor",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookId` TEXT NOT NULL, `authorId` TEXT NOT NULL, PRIMARY KEY(`bookId`, `authorId`), FOREIGN KEY(`bookId`) REFERENCES `Book`(`bookId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`authorId`) REFERENCES `Author`(`authorId`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
-        "fields": [
-          {
-            "fieldPath": "bookId",
-            "columnName": "bookId",
-            "affinity": "TEXT",
-            "notNull": true
-          },
-          {
-            "fieldPath": "authorId",
-            "columnName": "authorId",
-            "affinity": "TEXT",
-            "notNull": true
-          }
-        ],
-        "primaryKey": {
-          "columnNames": [
-            "bookId",
-            "authorId"
-          ],
-          "autoGenerate": false
-        },
-        "indices": [],
-        "foreignKeys": [
-          {
-            "table": "Book",
-            "onDelete": "CASCADE",
-            "onUpdate": "CASCADE",
-            "columns": [
-              "bookId"
-            ],
-            "referencedColumns": [
-              "bookId"
-            ]
-          },
-          {
-            "table": "Author",
-            "onDelete": "CASCADE",
-            "onUpdate": "CASCADE",
-            "columns": [
-              "authorId"
-            ],
-            "referencedColumns": [
-              "authorId"
-            ]
-          }
-        ]
-      }
-    ],
-    "setupQueries": [
-      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"64fa560604c57044726b190dadbd8258\")"
-    ]
-  }
-}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.TestDatabase/1.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.TestDatabase/1.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.TestDatabase/1.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.TestDatabase/1.json
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/1.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/1.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/1.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/1.json
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/2.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/2.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/2.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/2.json
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/3.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/3.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/3.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/3.json
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/4.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/4.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/4.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/4.json
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/5.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/5.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/5.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/5.json
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/6.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/6.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/6.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/6.json
diff --git a/room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/7.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/7.json
similarity index 100%
rename from room/integration-tests/kotlintestapp/schemas/android.arch.persistence.room.integration.kotlintestapp.migration.MigrationDbKotlin/7.json
rename to room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.MigrationDbKotlin/7.json
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/TestDatabase.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/TestDatabase.kt
deleted file mode 100644
index 33162ee..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/TestDatabase.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp
-
-import android.arch.persistence.room.Database
-import android.arch.persistence.room.RoomDatabase
-import android.arch.persistence.room.integration.kotlintestapp.dao.BooksDao
-import android.arch.persistence.room.integration.kotlintestapp.dao.DependencyDao
-import android.arch.persistence.room.integration.kotlintestapp.dao.DerivedDao
-import android.arch.persistence.room.integration.kotlintestapp.vo.Author
-import android.arch.persistence.room.integration.kotlintestapp.vo.Book
-import android.arch.persistence.room.integration.kotlintestapp.vo.BookAuthor
-import android.arch.persistence.room.integration.kotlintestapp.vo.DataClassFromDependency
-import android.arch.persistence.room.integration.kotlintestapp.vo.NoArgClass
-import android.arch.persistence.room.integration.kotlintestapp.vo.Publisher
-
-@Database(entities = [Book::class, Author::class, Publisher::class, BookAuthor::class,
-    NoArgClass::class, DataClassFromDependency::class], version = 1)
-abstract class TestDatabase : RoomDatabase() {
-
-    abstract fun booksDao(): BooksDao
-
-    abstract fun derivedDao(): DerivedDao
-
-    abstract fun dependencyDao(): DependencyDao
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/BaseDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/BaseDao.kt
deleted file mode 100644
index a80e840..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/BaseDao.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.dao
-
-import android.arch.persistence.room.Delete
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.OnConflictStrategy
-import android.arch.persistence.room.Update
-
-interface BaseDao<T> {
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    fun insert(t: T)
-
-    @Insert
-    fun insertAll(t: List<T>)
-
-    @Insert
-    fun insertAllArg(vararg t: T)
-
-    @Update
-    fun update(t: T)
-
-    @Delete
-    fun delete(t: T)
-}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/BooksDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/BooksDao.kt
deleted file mode 100644
index a3f3934..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/BooksDao.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.dao
-
-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.Transaction
-import android.arch.persistence.room.TypeConverters
-import android.arch.persistence.room.integration.kotlintestapp.vo.Author
-import android.arch.persistence.room.integration.kotlintestapp.vo.Book
-import android.arch.persistence.room.integration.kotlintestapp.vo.BookAuthor
-import android.arch.persistence.room.integration.kotlintestapp.vo.BookWithPublisher
-import android.arch.persistence.room.integration.kotlintestapp.vo.Lang
-import android.arch.persistence.room.integration.kotlintestapp.vo.Publisher
-import android.arch.persistence.room.integration.kotlintestapp.vo.PublisherWithBookSales
-import android.arch.persistence.room.integration.kotlintestapp.vo.PublisherWithBooks
-import com.google.common.base.Optional
-import com.google.common.util.concurrent.ListenableFuture
-import io.reactivex.Flowable
-import io.reactivex.Maybe
-import io.reactivex.Single
-
-@Dao
-interface BooksDao {
-
-    @Insert
-    fun addPublishers(vararg publishers: Publisher)
-
-    @Delete
-    fun deletePublishers(vararg publishers: Publisher)
-
-    @Insert
-    fun addAuthors(vararg authors: Author)
-
-    @Query("SELECT * FROM author WHERE authorId = :authorId")
-    fun getAuthor(authorId: String): Author
-
-    @Insert
-    fun addBooks(vararg books: Book)
-
-    @Insert
-    fun addBookAuthors(vararg bookAuthors: BookAuthor)
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBook(bookId: String): Book
-
-    @Query("""SELECT * FROM book WHERE
-            bookId IN(:bookIds)
-            order by bookId DESC""")
-    fun getBooksMultiLineQuery(bookIds: List<String>): List<Book>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookLiveData(bookId: String): LiveData<Book>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookFlowable(bookId: String): Flowable<Book>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookJavaOptional(bookId: String): java.util.Optional<Book>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookListenableFuture(bookId: String): ListenableFuture<Book>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookOptional(bookId: String): Optional<Book>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookOptionalFlowable(bookId: String): Flowable<Optional<Book>>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookOptionalListenableFuture(bookId: String): ListenableFuture<Optional<Book>>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookSingle(bookId: String): Single<Book>
-
-    @Query("SELECT * FROM book WHERE bookId = :bookId")
-    fun getBookMaybe(bookId: String): Maybe<Book>
-
-    @Query("SELECT * FROM book INNER JOIN publisher " +
-            "ON book.bookPublisherId = publisher.publisherId ")
-    fun getBooksWithPublisher(): List<BookWithPublisher>
-
-    @Query("SELECT * FROM book INNER JOIN publisher " +
-            "ON book.bookPublisherId = publisher.publisherId ")
-    fun getBooksWithPublisherLiveData(): LiveData<List<BookWithPublisher>>
-
-    @Query("SELECT * FROM book INNER JOIN publisher " +
-            "ON book.bookPublisherId = publisher.publisherId ")
-    fun getBooksWithPublisherFlowable(): Flowable<List<BookWithPublisher>>
-
-    @Query("SELECT * FROM book INNER JOIN publisher " +
-            "ON book.bookPublisherId = publisher.publisherId ")
-    fun getBooksWithPublisherListenableFuture(): ListenableFuture<List<BookWithPublisher>>
-
-    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
-    fun getPublisherWithBooks(publisherId: String): PublisherWithBooks
-
-    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
-    fun getPublisherWithBookSales(publisherId: String): PublisherWithBookSales
-
-    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
-    fun getPublisherWithBooksLiveData(publisherId: String): LiveData<PublisherWithBooks>
-
-    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
-    fun getPublisherWithBooksFlowable(publisherId: String): Flowable<PublisherWithBooks>
-
-    @Query("UPDATE book SET title = :title WHERE bookId = :bookId")
-    fun updateBookTitle(bookId: String, title: String?)
-
-    @Query("SELECT * FROM book WHERE languages & :langs != 0 ORDER BY bookId ASC")
-    @TypeConverters(Lang::class)
-    fun findByLanguages(langs: Set<Lang>): List<Book>
-
-    @Transaction
-    fun deleteAndAddPublisher(oldPublisher: Publisher, newPublisher: Publisher,
-            fail: Boolean = false) {
-        deletePublishers(oldPublisher)
-        if (fail) {
-            throw RuntimeException()
-        }
-        addPublishers(newPublisher)
-    }
-
-    @Query("SELECT * FROM Publisher")
-    fun getPublishers(): List<Publisher>
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/DerivedDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/DerivedDao.kt
deleted file mode 100644
index 5245a01..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/dao/DerivedDao.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.dao
-
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.integration.kotlintestapp.vo.Author
-
-@Dao
-interface DerivedDao : BaseDao<Author> {
-
-    @Query("SELECT * FROM author WHERE authorId = :authorId")
-    fun getAuthor(authorId: String): Author
-}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/migration/MigrationDbKotlin.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/migration/MigrationDbKotlin.kt
deleted file mode 100644
index 67374a5..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/migration/MigrationDbKotlin.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.migration
-
-import android.arch.persistence.db.SupportSQLiteDatabase
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Database
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.ForeignKey
-import android.arch.persistence.room.Ignore
-import android.arch.persistence.room.Index
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.RoomDatabase
-import android.content.ContentValues
-import android.database.sqlite.SQLiteDatabase
-
-@Database(version = MigrationDbKotlin.LATEST_VERSION,
-        entities = arrayOf(MigrationDbKotlin.Entity1::class, MigrationDbKotlin.Entity2::class,
-                MigrationDbKotlin.Entity4::class))
-abstract class MigrationDbKotlin : RoomDatabase() {
-
-    internal abstract fun dao(): MigrationDao
-
-    @Entity(indices = arrayOf(Index(value = "name", unique = true)))
-    data class Entity1(@PrimaryKey var id: Int = 0, var name: String?) {
-
-        companion object {
-            val TABLE_NAME = "Entity1"
-        }
-    }
-
-    @Entity
-    open class Entity2(@PrimaryKey var id: Int = 0, var addedInV3: String?, var name: String?) {
-        companion object {
-            val TABLE_NAME = "Entity2"
-        }
-    }
-
-    @Entity
-    data class Entity3(@PrimaryKey var id: Int = 0, @Ignore var removedInV5: String?,
-                       var name: String?) { // added in version 4, removed at 6
-        companion object {
-            val TABLE_NAME = "Entity3"
-        }
-    }
-
-    @Entity(foreignKeys = arrayOf(ForeignKey(entity = Entity1::class,
-            parentColumns = arrayOf("name"),
-            childColumns = arrayOf("name"),
-            deferred = true)))
-    data class Entity4(@PrimaryKey var id: Int = 0, var name: String?) {
-        companion object {
-            val TABLE_NAME = "Entity4"
-        }
-    }
-
-    @Dao
-    internal interface MigrationDao {
-        @Query("SELECT * from Entity1 ORDER BY id ASC")
-        fun loadAllEntity1s(): List<Entity1>
-
-        @Query("SELECT * from Entity2 ORDER BY id ASC")
-        fun loadAllEntity2s(): List<Entity2>
-
-        @Query("SELECT * from Entity2 ORDER BY id ASC")
-        fun loadAllEntity2sAsPojo(): List<Entity2Pojo>
-
-        @Insert
-        fun insert(vararg entity2: Entity2)
-    }
-
-    internal class Entity2Pojo(id: Int, addedInV3: String?, name: String?)
-        : Entity2(id, addedInV3, name)
-
-    /**
-     * not a real dao because database will change.
-     */
-    internal class Dao_V1(val mDb: SupportSQLiteDatabase) {
-
-        fun insertIntoEntity1(id: Int, name: String) {
-            val values = ContentValues()
-            values.put("id", id)
-            values.put("name", name)
-            val insertionId = mDb.insert(Entity1.TABLE_NAME,
-                    SQLiteDatabase.CONFLICT_REPLACE, values)
-            if (insertionId == -1L) {
-                throw RuntimeException("test sanity failure")
-            }
-        }
-    }
-
-    /**
-     * not a real dao because database will change.
-     */
-    internal class Dao_V2(val mDb: SupportSQLiteDatabase) {
-
-        fun insertIntoEntity2(id: Int, name: String) {
-            val values = ContentValues()
-            values.put("id", id)
-            values.put("name", name)
-            val insertionId = mDb.insert(Entity2.TABLE_NAME,
-                    SQLiteDatabase.CONFLICT_REPLACE, values)
-            if (insertionId == -1L) {
-                throw RuntimeException("test sanity failure")
-            }
-        }
-    }
-
-    companion object {
-        const val LATEST_VERSION = 7
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
deleted file mode 100644
index b684923..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.migration
-
-import android.arch.persistence.db.SupportSQLiteDatabase
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory
-import android.arch.persistence.room.Room
-import android.arch.persistence.room.migration.Migration
-import android.arch.persistence.room.testing.MigrationTestHelper
-import android.arch.persistence.room.util.TableInfo
-import android.support.test.InstrumentationRegistry
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.containsString
-import org.hamcrest.CoreMatchers.instanceOf
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Rule
-import org.junit.Test
-import java.io.FileNotFoundException
-import java.io.IOException
-
-class MigrationKotlinTest {
-
-    @get:Rule
-    var helper: MigrationTestHelper = MigrationTestHelper(
-            InstrumentationRegistry.getInstrumentation(),
-            MigrationDbKotlin::class.java.canonicalName,
-            FrameworkSQLiteOpenHelperFactory())
-
-    companion object {
-        val TEST_DB = "migration-test"
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun giveBadResource() {
-        val helper = MigrationTestHelper(
-                InstrumentationRegistry.getInstrumentation(),
-                "foo", FrameworkSQLiteOpenHelperFactory())
-        try {
-            helper.createDatabase(TEST_DB, 1)
-            throw AssertionError("must have failed with missing file exception")
-        } catch (exception: FileNotFoundException) {
-            assertThat<String>(exception.message, containsString("Cannot find"))
-        }
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun startInCurrentVersion() {
-        val db = helper.createDatabase(TEST_DB,
-                MigrationDbKotlin.LATEST_VERSION)
-        val dao = MigrationDbKotlin.Dao_V1(db)
-        dao.insertIntoEntity1(2, "x")
-        db.close()
-        val migrationDb = getLatestDb()
-        val items = migrationDb.dao().loadAllEntity1s()
-        helper.closeWhenFinished(migrationDb)
-        assertThat<Int>(items.size, `is`<Int>(1))
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun addTable() {
-        var db = helper.createDatabase(TEST_DB, 1)
-        val dao = MigrationDbKotlin.Dao_V1(db)
-        dao.insertIntoEntity1(2, "foo")
-        dao.insertIntoEntity1(3, "bar")
-        db.close()
-        db = helper.runMigrationsAndValidate(TEST_DB, 2, true,
-                MIGRATION_1_2)
-        MigrationDbKotlin.Dao_V2(db).insertIntoEntity2(3, "blah")
-        db.close()
-        val migrationDb = getLatestDb()
-        val entity1s = migrationDb.dao().loadAllEntity1s()
-
-        assertThat(entity1s.size, `is`(2))
-        val entity2 = MigrationDbKotlin.Entity2(2, null, "bar")
-        // assert no error happens
-        migrationDb.dao().insert(entity2)
-        val entity2s = migrationDb.dao().loadAllEntity2s()
-        assertThat(entity2s.size, `is`(2))
-    }
-
-    private fun getLatestDb(): MigrationDbKotlin {
-        val db = Room.databaseBuilder(
-                InstrumentationRegistry.getInstrumentation().targetContext,
-                MigrationDbKotlin::class.java, TEST_DB).addMigrations(*ALL_MIGRATIONS).build()
-        // trigger open
-        db.beginTransaction()
-        db.endTransaction()
-        helper.closeWhenFinished(db)
-        return db
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun addTableFailure() {
-        testFailure(1, 2)
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun addColumnFailure() {
-        val db = helper.createDatabase(TEST_DB, 2)
-        db.close()
-        var caught: IllegalStateException? = null
-        try {
-            helper.runMigrationsAndValidate(TEST_DB, 3, true,
-                    EmptyMigration(2, 3))
-        } catch (ex: IllegalStateException) {
-            caught = ex
-        }
-
-        assertThat<IllegalStateException>(caught,
-                instanceOf<IllegalStateException>(IllegalStateException::class.java))
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun addColumn() {
-        val db = helper.createDatabase(TEST_DB, 2)
-        val v2Dao = MigrationDbKotlin.Dao_V2(db)
-        v2Dao.insertIntoEntity2(7, "blah")
-        db.close()
-        helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3)
-        // trigger open.
-        val migrationDb = getLatestDb()
-        val entity2s = migrationDb.dao().loadAllEntity2s()
-        assertThat(entity2s.size, `is`(1))
-        assertThat<String>(entity2s[0].name, `is`("blah"))
-        assertThat<String>(entity2s[0].addedInV3, `is`<Any>(nullValue()))
-
-        val entity2Pojos = migrationDb.dao().loadAllEntity2sAsPojo()
-        assertThat(entity2Pojos.size, `is`(1))
-        assertThat<String>(entity2Pojos[0].name, `is`("blah"))
-        assertThat<String>(entity2Pojos[0].addedInV3, `is`<Any>(nullValue()))
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun failedToRemoveColumn() {
-        testFailure(4, 5)
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun removeColumn() {
-        helper.createDatabase(TEST_DB, 4)
-        val db = helper.runMigrationsAndValidate(TEST_DB,
-                5, true, MIGRATION_4_5)
-        val info = TableInfo.read(db, MigrationDbKotlin.Entity3.TABLE_NAME)
-        assertThat(info.columns.size, `is`(2))
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun dropTable() {
-        helper.createDatabase(TEST_DB, 5)
-        val db = helper.runMigrationsAndValidate(TEST_DB,
-                6, true, MIGRATION_5_6)
-        val info = TableInfo.read(db, MigrationDbKotlin.Entity3.TABLE_NAME)
-        assertThat(info.columns.size, `is`(0))
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun failedToDropTable() {
-        testFailure(5, 6)
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun failedToDropTableDontVerify() {
-        helper.createDatabase(TEST_DB, 5)
-        val db = helper.runMigrationsAndValidate(TEST_DB,
-                6, false, EmptyMigration(5, 6))
-        val info = TableInfo.read(db, MigrationDbKotlin.Entity3.TABLE_NAME)
-        assertThat(info.columns.size, `is`(2))
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun failedForeignKey() {
-        val db = helper.createDatabase(TEST_DB, 6)
-        db.close()
-        var throwable: Throwable? = null
-        try {
-            helper.runMigrationsAndValidate(TEST_DB,
-                    7, false, object : Migration(6, 7) {
-                override fun migrate(database: SupportSQLiteDatabase) {
-                    database.execSQL("CREATE TABLE Entity4 (`id` INTEGER, `name` TEXT,"
-                            + " PRIMARY KEY(`id`))")
-                }
-            })
-        } catch (t: Throwable) {
-            throwable = t
-        }
-
-        assertThat<Throwable>(throwable, instanceOf<Throwable>(IllegalStateException::class.java))
-
-        assertThat<String>(throwable!!.message, containsString("Migration failed"))
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun newTableWithForeignKey() {
-        helper.createDatabase(TEST_DB, 6)
-        val db = helper.runMigrationsAndValidate(TEST_DB,
-                7, false, MIGRATION_6_7)
-        val info = TableInfo.read(db, MigrationDbKotlin.Entity4.TABLE_NAME)
-        assertThat(info.foreignKeys.size, `is`(1))
-    }
-
-    @Throws(IOException::class)
-    private fun testFailure(startVersion: Int, endVersion: Int) {
-        val db = helper.createDatabase(TEST_DB, startVersion)
-        db.close()
-        var throwable: Throwable? = null
-        try {
-            helper.runMigrationsAndValidate(TEST_DB, endVersion, true,
-                    EmptyMigration(startVersion, endVersion))
-        } catch (t: Throwable) {
-            throwable = t
-        }
-
-        assertThat<Throwable>(throwable, instanceOf<Throwable>(IllegalStateException::class.java))
-        assertThat<String>(throwable!!.message, containsString("Migration failed"))
-    }
-
-    internal val MIGRATION_1_2: Migration = object : Migration(1, 2) {
-        override fun migrate(database: SupportSQLiteDatabase) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity2` (`id` INTEGER NOT NULL,"
-                    + " `name` TEXT, PRIMARY KEY(`id`))")
-        }
-    }
-
-    internal val MIGRATION_2_3: Migration = object : Migration(2, 3) {
-        override fun migrate(database: SupportSQLiteDatabase) {
-            database.execSQL("ALTER TABLE " + MigrationDbKotlin.Entity2.TABLE_NAME
-                    + " ADD COLUMN addedInV3 TEXT")
-        }
-    }
-
-    internal val MIGRATION_3_4: Migration = object : Migration(3, 4) {
-        override fun migrate(database: SupportSQLiteDatabase) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3` (`id` INTEGER NOT NULL,"
-                    + " `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))")
-        }
-    }
-
-    internal val MIGRATION_4_5: Migration = object : Migration(4, 5) {
-        override fun migrate(database: SupportSQLiteDatabase) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3_New` (`id` INTEGER NOT NULL,"
-                    + " `name` TEXT, PRIMARY KEY(`id`))")
-            database.execSQL("INSERT INTO Entity3_New(`id`, `name`) "
-                    + "SELECT `id`, `name` FROM Entity3")
-            database.execSQL("DROP TABLE Entity3")
-            database.execSQL("ALTER TABLE Entity3_New RENAME TO Entity3")
-        }
-    }
-
-    internal val MIGRATION_5_6: Migration = object : Migration(5, 6) {
-        override fun migrate(database: SupportSQLiteDatabase) {
-            database.execSQL("DROP TABLE " + MigrationDbKotlin.Entity3.TABLE_NAME)
-        }
-    }
-
-    internal val MIGRATION_6_7: Migration = object : Migration(6, 7) {
-        override fun migrate(database: SupportSQLiteDatabase) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS "
-                    + MigrationDbKotlin.Entity4.TABLE_NAME
-                    + " (`id` INTEGER NOT NULL, `name` TEXT, 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 "
-                    + MigrationDbKotlin.Entity1.TABLE_NAME + " (`name`)")
-        }
-    }
-
-    private val ALL_MIGRATIONS = arrayOf(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
-            MIGRATION_5_6, MIGRATION_6_7)
-
-    internal class EmptyMigration(startVersion: Int, endVersion: Int)
-        : Migration(startVersion, endVersion) {
-
-        override fun migrate(database: SupportSQLiteDatabase) {
-            // do nothing
-        }
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/BooksDaoTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/BooksDaoTest.kt
deleted file mode 100644
index 5f355fd..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/BooksDaoTest.kt
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.test
-
-import android.arch.core.executor.ArchTaskExecutor
-import android.arch.persistence.room.integration.kotlintestapp.vo.Author
-import android.arch.persistence.room.integration.kotlintestapp.vo.Book
-import android.arch.persistence.room.integration.kotlintestapp.vo.BookWithPublisher
-import android.arch.persistence.room.integration.kotlintestapp.vo.Lang
-import android.arch.persistence.room.integration.kotlintestapp.vo.Publisher
-import android.database.sqlite.SQLiteConstraintException
-import android.support.test.filters.SmallTest
-import com.google.common.base.Optional
-import io.reactivex.Flowable
-import io.reactivex.schedulers.Schedulers
-import io.reactivex.subscribers.TestSubscriber
-import org.hamcrest.CoreMatchers
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.equalTo
-import org.hamcrest.CoreMatchers.instanceOf
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Assert.assertNotNull
-import org.junit.Test
-import java.util.Date
-
-@SmallTest
-class BooksDaoTest : TestDatabaseTest() {
-
-    @Test
-    fun bookById() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        assertThat(booksDao.getBook(TestUtil.BOOK_1.bookId), `is`<Book>(TestUtil.BOOK_1))
-    }
-
-    @Test
-    fun bookByIdJavaOptional() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        assertThat(
-                booksDao.getBookJavaOptional(TestUtil.BOOK_1.bookId),
-                `is`<java.util.Optional<Book>>(java.util.Optional.of(TestUtil.BOOK_1)))
-    }
-
-    @Test
-    fun bookByIdJavaOptionalEmpty() {
-        assertThat(
-                booksDao.getBookJavaOptional(TestUtil.BOOK_1.bookId),
-                `is`<java.util.Optional<Book>>(java.util.Optional.empty()))
-    }
-
-    @Test
-    fun bookByIdListenableFuture() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        assertThat(
-                booksDao.getBookListenableFuture(TestUtil.BOOK_1.bookId).get(),
-                `is`<Book>(TestUtil.BOOK_1))
-    }
-
-    @Test
-    fun bookByIdOptional() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        assertThat(
-                booksDao.getBookOptional(TestUtil.BOOK_1.bookId),
-                `is`<Optional<Book>>(Optional.of(TestUtil.BOOK_1)))
-    }
-
-    @Test
-    fun bookByIdOptionalListenableFuture() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        assertThat(
-                booksDao.getBookOptionalListenableFuture(TestUtil.BOOK_1.bookId).get(),
-                `is`<Optional<Book>>(Optional.of(TestUtil.BOOK_1)))
-    }
-
-    @Test
-    fun bookByIdOptionalListenableFutureAbsent() {
-        assertThat(
-                booksDao.getBookOptionalListenableFuture(TestUtil.BOOK_1.bookId).get(),
-                `is`<Optional<Book>>(Optional.absent()))
-    }
-
-    @Test
-    fun bookByIdOptionalAbsent() {
-        assertThat(
-                booksDao.getBookOptional(TestUtil.BOOK_1.bookId),
-                `is`<Optional<Book>>(Optional.absent()))
-    }
-
-    @Test
-    fun bookByIdOptionalFlowable() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        val subscriber = TestSubscriber<Optional<Book>>()
-        val flowable: Flowable<Optional<Book>> =
-                booksDao.getBookOptionalFlowable(TestUtil.BOOK_1.bookId)
-        flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
-                .subscribeWith(subscriber)
-
-        assertThat(subscriber.values().size, `is`(1))
-        assertThat(subscriber.values()[0], `is`(Optional.of(TestUtil.BOOK_1)))
-    }
-
-    @Test
-    fun bookByIdOptionalFlowableAbsent() {
-        val subscriber = TestSubscriber<Optional<Book>>()
-        val flowable: Flowable<Optional<Book>> =
-                booksDao.getBookOptionalFlowable(TestUtil.BOOK_1.bookId)
-        flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
-                .subscribeWith(subscriber)
-
-        assertThat(subscriber.values().size, `is`(1))
-        assertThat(subscriber.values()[0], `is`(Optional.absent()))
-    }
-
-    @Test
-    fun bookWithPublisher() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        val expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
-                TestUtil.PUBLISHER)
-        val expectedList = ArrayList<BookWithPublisher>()
-        expectedList.add(expected)
-
-        assertThat(database.booksDao().getBooksWithPublisher(),
-                `is`<List<BookWithPublisher>>(expectedList))
-    }
-
-    @Test
-    fun bookWithPublisherListenableFuture() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        val expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
-                TestUtil.PUBLISHER)
-        val expectedList = ArrayList<BookWithPublisher>()
-        expectedList.add(expected)
-
-        assertThat(database.booksDao().getBooksWithPublisherListenableFuture().get(),
-                `is`<List<BookWithPublisher>>(expectedList))
-    }
-
-    @Test
-    fun updateBookWithNullTitle() {
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        var throwable: Throwable? = null
-        try {
-            booksDao.updateBookTitle(TestUtil.BOOK_1.bookId, null)
-        } catch (t: Throwable) {
-            throwable = t
-        }
-        assertNotNull(throwable)
-        assertThat<Throwable>(throwable, instanceOf<Throwable>(SQLiteConstraintException::class
-                .java))
-    }
-
-    @Test
-    fun publisherWithBooks() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
-
-        val actualPublisherWithBooks = booksDao.getPublisherWithBooks(
-                TestUtil.PUBLISHER.publisherId)
-
-        assertThat(actualPublisherWithBooks.publisher, `is`<Publisher>(TestUtil.PUBLISHER))
-        assertThat(actualPublisherWithBooks.books?.size, `is`(2))
-        assertThat(actualPublisherWithBooks.books?.get(0), `is`<Book>(TestUtil.BOOK_1))
-        assertThat(actualPublisherWithBooks.books?.get(1), `is`<Book>(TestUtil.BOOK_2))
-    }
-
-    @Test // b/68077506
-    fun publisherWithBookSales() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
-        val actualPublisherWithBooks = booksDao.getPublisherWithBookSales(
-                TestUtil.PUBLISHER.publisherId)
-
-        assertThat(actualPublisherWithBooks.publisher, `is`<Publisher>(TestUtil.PUBLISHER))
-        assertThat(actualPublisherWithBooks.sales, `is`(listOf(TestUtil.BOOK_1.salesCnt,
-                TestUtil.BOOK_2.salesCnt)))
-    }
-
-    @Test
-    fun insertAuthorWithAllFields() {
-        val author = Author("id", "name", Date(), ArrayList())
-        database.booksDao().addAuthors(author)
-
-        val authorDb = database.booksDao().getAuthor(author.authorId)
-
-        assertThat(authorDb, CoreMatchers.`is`<Author>(author))
-    }
-
-    @Test
-    fun insertInInheritedDao() {
-        database.derivedDao().insert(TestUtil.AUTHOR_1)
-
-        val author = database.derivedDao().getAuthor(TestUtil.AUTHOR_1.authorId)
-
-        assertThat(author, CoreMatchers.`is`<Author>(TestUtil.AUTHOR_1))
-    }
-
-    @Test
-    fun findBooksInMultiLineQuery() {
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-        booksDao.addBooks(TestUtil.BOOK_2)
-
-        val books = database.booksDao().getBooksMultiLineQuery(arrayListOf(
-                TestUtil.BOOK_1.bookId,
-                TestUtil.BOOK_2.bookId))
-        assertThat(books, `is`(listOf(TestUtil.BOOK_2, TestUtil.BOOK_1)))
-    }
-
-    @Test
-    fun findBooksByLanguage() {
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        val book1 = TestUtil.BOOK_1.copy(languages = setOf(Lang.TR))
-        val book2 = TestUtil.BOOK_2.copy(languages = setOf(Lang.ES, Lang.TR))
-        val book3 = TestUtil.BOOK_3.copy(languages = setOf(Lang.EN))
-        booksDao.addBooks(book1, book2, book3)
-
-        assertThat(booksDao.findByLanguages(setOf(Lang.EN, Lang.TR)),
-                `is`(listOf(book1, book2, book3)))
-
-        assertThat(booksDao.findByLanguages(setOf(Lang.TR)),
-                `is`(listOf(book1, book2)))
-
-        assertThat(booksDao.findByLanguages(setOf(Lang.ES)),
-                `is`(listOf(book2)))
-
-        assertThat(booksDao.findByLanguages(setOf(Lang.EN)),
-                `is`(listOf(book3)))
-    }
-
-    @Test
-    fun insertVarargInInheritedDao() {
-        database.derivedDao().insertAllArg(TestUtil.AUTHOR_1, TestUtil.AUTHOR_2)
-
-        val author = database.derivedDao().getAuthor(TestUtil.AUTHOR_1.authorId)
-
-        assertThat(author, CoreMatchers.`is`<Author>(TestUtil.AUTHOR_1))
-    }
-
-    @Test
-    fun insertListInInheritedDao() {
-        database.derivedDao().insertAll(listOf(TestUtil.AUTHOR_1))
-
-        val author = database.derivedDao().getAuthor(TestUtil.AUTHOR_1.authorId)
-
-        assertThat(author, CoreMatchers.`is`<Author>(TestUtil.AUTHOR_1))
-    }
-
-    @Test
-    fun deleteAndAddPublisher() {
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.getPublishers().run {
-            assertThat(this.size, `is`(1))
-            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER)))
-        }
-        booksDao.deleteAndAddPublisher(TestUtil.PUBLISHER, TestUtil.PUBLISHER2)
-        booksDao.getPublishers().run {
-            assertThat(this.size, `is`(1))
-            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER2)))
-        }
-    }
-
-    @Test
-    fun deleteAndAddPublisher_failure() {
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.getPublishers().run {
-            assertThat(this.size, `is`(1))
-            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER)))
-        }
-        var throwable: Throwable? = null
-        try {
-            booksDao.deleteAndAddPublisher(TestUtil.PUBLISHER, TestUtil.PUBLISHER2, true)
-        } catch (e: RuntimeException) {
-            throwable = e
-        }
-        assertThat(throwable, `is`(notNullValue()))
-        booksDao.getPublishers().run {
-            assertThat(this.size, `is`(1))
-            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER)))
-        }
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/DependencyDaoTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/DependencyDaoTest.kt
deleted file mode 100644
index fd6de54..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/DependencyDaoTest.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2018 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.kotlintestapp.test
-
-import android.arch.persistence.room.integration.kotlintestapp.dao.DependencyDao
-import android.arch.persistence.room.integration.kotlintestapp.vo.DataClassFromDependency
-import android.arch.persistence.room.integration.kotlintestapp.vo.EmbeddedFromDependency
-import android.arch.persistence.room.integration.kotlintestapp.vo.PojoFromDependency
-import android.os.Build
-import android.support.test.filters.SdkSuppress
-import android.support.test.runner.AndroidJUnit4
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.CoreMatchers.notNullValue
-import org.hamcrest.CoreMatchers.nullValue
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class DependencyDaoTest : TestDatabaseTest() {
-    lateinit var dao: DependencyDao
-    @Before
-    fun init() {
-        dao = database.dependencyDao()
-    }
-
-    @Test
-    fun insertAndGet() {
-        val data = insertSample(3)
-        assertThat(dao.selectAll(), `is`(listOf(data)))
-    }
-
-    @Test
-    fun insertAndGetByQuery() {
-        val data = insertSample(3)
-        assertThat(dao.findById(3), `is`(data))
-        assertThat(dao.findById(5), `is`(nullValue()))
-    }
-
-    @Test
-    fun insertAndGetByQuery_embedded() {
-        val data = insertSample(3)
-        assertThat(dao.findEmbedded(3), `is`(EmbeddedFromDependency(data)))
-        assertThat(dao.findEmbedded(5), `is`(nullValue()))
-    }
-
-    @Test
-    fun insertAndGetByQuery_pojo() {
-        val data = insertSample(3)
-        assertThat(dao.findPojo(3), `is`(PojoFromDependency(
-                id = data.id,
-                name = data.name)))
-        assertThat(dao.findPojo(5), `is`(nullValue()))
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    @Test
-    fun getRelation() {
-        val foo1 = DataClassFromDependency(
-                id = 3,
-                name = "foo"
-        )
-        val foo2 = DataClassFromDependency(
-                id = 4,
-                name = "foo"
-        )
-        val bar = DataClassFromDependency(
-                id = 5,
-                name = "bar"
-        )
-        dao.insert(foo1, foo2, bar)
-        val fooList = dao.relation("foo")
-        assertThat(fooList.sharedName, `is`("foo"))
-        assertThat(fooList, `is`(notNullValue()))
-        assertThat(fooList.dataItems, `is`(listOf(foo1, foo2)))
-
-        val barList = dao.relation("bar")
-        assertThat(barList.sharedName, `is`("bar"))
-        assertThat(barList, `is`(notNullValue()))
-        assertThat(barList.dataItems, `is`(listOf(bar)))
-
-        val bazList = dao.relation("baz")
-        assertThat(bazList.sharedName, `is`("baz"))
-        assertThat(bazList, `is`(notNullValue()))
-        assertThat(bazList.dataItems, `is`(emptyList()))
-    }
-
-    private fun insertSample(id: Int): DataClassFromDependency {
-        val data = DataClassFromDependency(
-                id = id,
-                name = "foo"
-        )
-        dao.insert(data)
-        return data
-    }
-}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/ItemWithNullableConstructor.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/ItemWithNullableConstructor.kt
deleted file mode 100644
index 091beae..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/ItemWithNullableConstructor.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2018 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.kotlintestapp.test
-
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Database
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.Room
-import android.arch.persistence.room.RoomDatabase
-import android.arch.persistence.room.RoomWarnings
-import android.support.test.InstrumentationRegistry
-import android.support.test.runner.AndroidJUnit4
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ItemWithNullableConstructor {
-    lateinit var db: Db
-    @Before
-    fun initDb() {
-        db = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
-                Db::class.java).build()
-    }
-
-    @After
-    fun closeDb() {
-        db.close()
-    }
-
-    @Test
-    fun insertWithNull() {
-        db.dao.insert(TestItem(null, null))
-        assertThat(db.dao.get(), `is`(TestItem(1, null)))
-    }
-
-    @Entity
-    data class TestItem(
-            @PrimaryKey(autoGenerate = true)
-            val id: Long? = null,
-            val nullable: Boolean?
-    )
-
-    @Dao
-    interface TestDao {
-        @Insert
-        fun insert(testItem: TestItem)
-
-        @Query("SELECT * FROM TestItem LIMIT 1")
-        fun get(): TestItem?
-    }
-
-    @Database(
-            version = 1,
-            entities = [TestItem::class],
-            exportSchema = false
-    )
-    @SuppressWarnings(RoomWarnings.MISSING_SCHEMA_LOCATION)
-    abstract class Db : RoomDatabase() {
-        abstract val dao: TestDao
-    }
-}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/LiveDataQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/LiveDataQueryTest.kt
deleted file mode 100644
index 70bc54c..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/LiveDataQueryTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.test
-
-import android.arch.persistence.room.integration.kotlintestapp.vo.Book
-import android.arch.persistence.room.integration.kotlintestapp.vo.BookWithPublisher
-import android.arch.persistence.room.integration.kotlintestapp.vo.Publisher
-import android.support.test.filters.SmallTest
-import org.hamcrest.CoreMatchers.`is`
-import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Test
-
-@SmallTest
-class LiveDataQueryTest : TestDatabaseTest() {
-
-    @Test
-    fun observeBooksById() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        val book = LiveDataTestUtil.getValue(booksDao.getBookLiveData(TestUtil.BOOK_1.bookId))
-
-        assertThat(book, `is`<Book>(TestUtil.BOOK_1))
-    }
-
-    @Test
-    fun observeBooksWithPublisher() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        var expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
-                TestUtil.PUBLISHER)
-        var expectedList = ArrayList<BookWithPublisher>()
-        expectedList.add(expected)
-
-        val actual = LiveDataTestUtil.getValue(booksDao.getBooksWithPublisherLiveData())
-        assertThat(actual, `is`<List<BookWithPublisher>>(expectedList))
-    }
-
-    @Test
-    fun publisherWithBooks() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
-
-        var actualPublisherWithBooks = LiveDataTestUtil.getValue(
-                booksDao.getPublisherWithBooksLiveData(TestUtil.PUBLISHER.publisherId))
-
-        assertThat(actualPublisherWithBooks.publisher, `is`<Publisher>(TestUtil.PUBLISHER))
-        assertThat(actualPublisherWithBooks.books?.size, `is`(2))
-        assertThat(actualPublisherWithBooks.books?.get(0), `is`<Book>(TestUtil.BOOK_1))
-        assertThat(actualPublisherWithBooks.books?.get(1), `is`<Book>(TestUtil.BOOK_2))
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/LiveDataTestUtil.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/LiveDataTestUtil.kt
deleted file mode 100644
index 5533f11..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/LiveDataTestUtil.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.test
-
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.Observer
-
-object LiveDataTestUtil {
-
-    @Throws(InterruptedException::class)
-    fun <T> getValue(liveData: LiveData<T>): T {
-        val data = arrayOfNulls<Any>(1)
-        val observer = object : Observer<T> {
-            override fun onChanged(o: T?) {
-                data[0] = o
-                liveData.removeObserver(this)
-            }
-        }
-        liveData.observeForever(observer)
-
-        return data[0] as T
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/RxJava2QueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/RxJava2QueryTest.kt
deleted file mode 100644
index 094eea8..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/RxJava2QueryTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.test
-
-import android.arch.persistence.room.EmptyResultSetException
-import android.arch.persistence.room.integration.kotlintestapp.vo.BookWithPublisher
-import android.support.test.filters.SmallTest
-import org.junit.Test
-
-@SmallTest
-class RxJava2QueryTest : TestDatabaseTest() {
-
-    @Test
-    fun observeBooksById() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        booksDao.getBookFlowable(TestUtil.BOOK_1.bookId)
-                .test()
-                .assertValue { book -> book == TestUtil.BOOK_1 }
-    }
-
-    @Test
-    fun observeBooksByIdSingle() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        booksDao.getBookSingle(TestUtil.BOOK_1.bookId)
-                .test()
-                .assertComplete()
-                .assertValue { book -> book == TestUtil.BOOK_1 }
-    }
-
-    @Test
-    fun observeBooksByIdSingle_noBook() {
-        booksDao.getBookSingle("x")
-                .test()
-                .assertError(EmptyResultSetException::class.java)
-    }
-
-    @Test
-    fun observeBooksByIdMaybe() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        booksDao.getBookMaybe(TestUtil.BOOK_1.bookId)
-                .test()
-                .assertComplete()
-                .assertValue { book -> book == TestUtil.BOOK_1 }
-    }
-
-    @Test
-    fun observeBooksByIdMaybe_noBook() {
-        booksDao.getBookMaybe("x")
-                .test()
-                .assertComplete()
-                .assertNoErrors()
-                .assertNoValues()
-    }
-
-    @Test
-    fun observeBooksWithPublisher() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1)
-
-        var expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
-                TestUtil.PUBLISHER)
-        var expectedList = ArrayList<BookWithPublisher>()
-        expectedList.add(expected)
-
-        booksDao.getBooksWithPublisherFlowable()
-                .test()
-                .assertValue(expectedList)
-    }
-
-    @Test
-    fun publisherWithBooks() {
-        booksDao.addAuthors(TestUtil.AUTHOR_1)
-        booksDao.addPublishers(TestUtil.PUBLISHER)
-        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
-
-        booksDao.getPublisherWithBooksFlowable(TestUtil.PUBLISHER.publisherId)
-                .test()
-                .assertValue {
-                    it.publisher == TestUtil.PUBLISHER
-                            && it.books?.size == 2
-                            && it.books?.get(0) == TestUtil.BOOK_1
-                            && it.books?.get(1) == TestUtil.BOOK_2
-                }
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/TestDatabaseTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/TestDatabaseTest.kt
deleted file mode 100644
index 520c4d3..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/TestDatabaseTest.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.test
-
-import android.arch.core.executor.testing.InstantTaskExecutorRule
-import android.arch.persistence.room.Room
-import android.arch.persistence.room.integration.kotlintestapp.dao.BooksDao
-import android.arch.persistence.room.integration.kotlintestapp.TestDatabase
-import android.support.test.InstrumentationRegistry
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-
-abstract class TestDatabaseTest {
-
-    @get:Rule
-    var instantTaskExecutorRule = InstantTaskExecutorRule()
-
-    protected lateinit var database: TestDatabase
-    protected lateinit var booksDao: BooksDao
-
-    @Before
-    @Throws(Exception::class)
-    fun setUp() {
-        database = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
-                TestDatabase::class.java)
-                // allowing main thread queries, just for testing
-                .allowMainThreadQueries()
-                .build()
-
-        booksDao = database.booksDao()
-    }
-
-    @After
-    @Throws(Exception::class)
-    fun tearDown() {
-        database.close()
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/TestUtil.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/TestUtil.kt
deleted file mode 100644
index 8c02a64..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/test/TestUtil.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.test
-
-import android.arch.persistence.room.integration.kotlintestapp.vo.Author
-import android.arch.persistence.room.integration.kotlintestapp.vo.Book
-import android.arch.persistence.room.integration.kotlintestapp.vo.BookAuthor
-import android.arch.persistence.room.integration.kotlintestapp.vo.Lang
-import android.arch.persistence.room.integration.kotlintestapp.vo.Publisher
-
-class TestUtil {
-
-    companion object {
-
-        val PUBLISHER = Publisher("ph1", "publisher 1")
-        val PUBLISHER2 = Publisher("ph2", "publisher 2")
-
-        val AUTHOR_1 = Author("a1", "author 1")
-        val AUTHOR_2 = Author("a2", "author 2")
-
-        val BOOK_1 = Book("b1", "book title 1", "ph1",
-                setOf(Lang.EN), 3)
-        val BOOK_2 = Book("b2", "book title 2", "ph1",
-                setOf(Lang.TR), 5)
-        val BOOK_3 = Book("b3", "book title 2", "ph1",
-                setOf(Lang.ES), 7)
-
-        val BOOK_AUTHOR_1_1 = BookAuthor(BOOK_1.bookId, AUTHOR_1.authorId)
-        val BOOK_AUTHOR_1_2 = BookAuthor(BOOK_1.bookId, AUTHOR_2.authorId)
-        val BOOK_AUTHOR_2_2 = BookAuthor(BOOK_2.bookId, AUTHOR_2.authorId)
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Author.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Author.kt
deleted file mode 100644
index 067fcb3..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Author.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.TypeConverters
-import java.util.Date
-
-@Entity
-@TypeConverters(DateConverter::class, StringToIntListConverters::class)
-data class Author(
-        @PrimaryKey val authorId: String,
-        val name: String,
-        val dateOfBirth: Date? = null,
-        val aList: List<Integer>? = null)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Book.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Book.kt
deleted file mode 100644
index 019f07f..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Book.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.ForeignKey
-import android.arch.persistence.room.PrimaryKey
-import android.arch.persistence.room.TypeConverters
-
-@Entity(foreignKeys = arrayOf(
-        ForeignKey(entity = Publisher::class,
-                parentColumns = arrayOf("publisherId"),
-                childColumns = arrayOf("bookPublisherId"),
-                deferred = true)))
-data class Book(
-        @PrimaryKey val bookId: String,
-        val title: String,
-        val bookPublisherId: String,
-        @field:TypeConverters(Lang::class)
-        val languages: Set<Lang>,
-        val salesCnt: Int)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/BookAuthor.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/BookAuthor.kt
deleted file mode 100644
index 5a21f40..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/BookAuthor.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.ForeignKey
-
-@Entity(foreignKeys = arrayOf(
-        ForeignKey(entity = Book::class,
-                parentColumns = arrayOf("bookId"),
-                childColumns = arrayOf("bookId"),
-                onUpdate = ForeignKey.CASCADE,
-                onDelete = ForeignKey.CASCADE,
-                deferred = true),
-        ForeignKey(entity = Author::class,
-                parentColumns = arrayOf("authorId"),
-                childColumns = arrayOf("authorId"),
-                onUpdate = ForeignKey.CASCADE,
-                onDelete = ForeignKey.CASCADE,
-                deferred = true)),
-        primaryKeys = arrayOf("bookId", "authorId"))
-data class BookAuthor(val bookId: String, val authorId: String)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/BookWithPublisher.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/BookWithPublisher.kt
deleted file mode 100644
index 418a0e2..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/BookWithPublisher.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.Embedded
-
-data class BookWithPublisher(val bookId: String, val title: String,
-                             @Embedded val publisher: Publisher)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/DateConverter.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/DateConverter.kt
deleted file mode 100755
index 62a3c21..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/DateConverter.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 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.kotlintestapp.vo
-
-import android.arch.persistence.room.TypeConverter
-
-import java.util.Date
-
-class DateConverter {
-    @TypeConverter
-    fun toDate(timestamp: Long?): Date? {
-        return if (timestamp == null) null else Date(timestamp)
-    }
-
-    @TypeConverter
-    fun toTimestamp(date: Date?): Long? {
-        return date?.time
-    }
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Lang.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Lang.kt
deleted file mode 100644
index 1d5fb5c..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Lang.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 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.kotlintestapp.vo
-
-import android.arch.persistence.room.TypeConverter
-
-/**
- * An enum class which gets saved as a bit set in the database.
- */
-enum class Lang {
-    TR,
-    EN,
-    ES;
-
-    companion object {
-        @JvmStatic
-        @TypeConverter
-        fun toInt(langs: Set<Lang>): Int {
-            return langs.fold(0) { left, lang ->
-                left.or(1 shl lang.ordinal)
-            }
-        }
-
-        @JvmStatic
-        @TypeConverter
-        fun toSet(value: Int): Set<Lang> {
-            return Lang.values().filter {
-                (1 shl it.ordinal).and(value) != 0
-            }.toSet()
-        }
-    }
-}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/NoArgClass.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/NoArgClass.kt
deleted file mode 100644
index e1e1a7d..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/NoArgClass.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-
-/**
- * just here to ensure that we handle no-arg constructors fine from kotlin.
- */
-@Entity
-data class NoArgClass(@PrimaryKey var id: Long = 0, var class_name: String = "")
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Publisher.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Publisher.kt
deleted file mode 100644
index ea1550a..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/Publisher.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-
-@Entity
-data class Publisher(@PrimaryKey val publisherId: String, val name: String)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/PublisherWithBookSales.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/PublisherWithBookSales.kt
deleted file mode 100644
index 0087331..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/PublisherWithBookSales.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 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.kotlintestapp.vo
-
-import android.arch.persistence.room.Embedded
-import android.arch.persistence.room.Relation
-
-data class PublisherWithBookSales @JvmOverloads constructor(
-        @Embedded
-        val publisher: Publisher,
-        @Relation(parentColumn = "publisherId", // publisher.publisherId
-                entityColumn = "bookPublisherId", // book.bookPublisherId
-                entity = Book::class,
-                projection = ["salesCnt"])
-        var sales: List<Int>? = emptyList())
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/PublisherWithBooks.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/PublisherWithBooks.kt
deleted file mode 100644
index fe482b1..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/PublisherWithBooks.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.Embedded
-import android.arch.persistence.room.Relation
-
-class PublisherWithBooks {
-    @Embedded var publisher: Publisher? = null
-    @Relation(parentColumn = "publisherId", // publisher.publisherId
-            entityColumn = "bookPublisherId", // book.bookPublisherId
-            entity = Book::class)
-    var books: List<Book>? = null
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/StringToIntListConverters.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/StringToIntListConverters.kt
deleted file mode 100644
index ec8fd4a..0000000
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/android/arch/persistence/room/integration/kotlintestapp/vo/StringToIntListConverters.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package android.arch.persistence.room.integration.kotlintestapp.vo
-
-import android.arch.persistence.room.TypeConverter
-import android.arch.persistence.room.util.StringUtil
-
-object StringToIntListConverters {
-    @TypeConverter
-    // Specifying that a static method should be generated. Otherwise, the compiler looks for the
-    // constructor of the class, and a object has a private constructor.
-    @JvmStatic
-    fun stringToIntList(data: String?): List<Int>? =
-            if (data == null) null else StringUtil.splitToIntList(data)
-
-    @TypeConverter
-    @JvmStatic
-    fun intListToString(ints: List<Int>?): String? =
-            if (ints == null) null else StringUtil.joinIntoString(ints)
-}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/TestDatabase.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/TestDatabase.kt
new file mode 100644
index 0000000..5e4e0e7
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/TestDatabase.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import androidx.room.integration.kotlintestapp.dao.BooksDao
+import androidx.room.integration.kotlintestapp.dao.DependencyDao
+import androidx.room.integration.kotlintestapp.dao.DerivedDao
+import androidx.room.integration.kotlintestapp.vo.Author
+import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.integration.kotlintestapp.vo.BookAuthor
+import androidx.room.integration.kotlintestapp.vo.DataClassFromDependency
+import androidx.room.integration.kotlintestapp.vo.NoArgClass
+import androidx.room.integration.kotlintestapp.vo.Publisher
+
+@Database(entities = [Book::class, Author::class, Publisher::class, BookAuthor::class,
+    NoArgClass::class, DataClassFromDependency::class], version = 1)
+abstract class TestDatabase : RoomDatabase() {
+
+    abstract fun booksDao(): BooksDao
+
+    abstract fun derivedDao(): DerivedDao
+
+    abstract fun dependencyDao(): DependencyDao
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BaseDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BaseDao.kt
new file mode 100644
index 0000000..a9aac29
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BaseDao.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.dao
+
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Update
+
+interface BaseDao<T> {
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insert(t: T)
+
+    @Insert
+    fun insertAll(t: List<T>)
+
+    @Insert
+    fun insertAllArg(vararg t: T)
+
+    @Update
+    fun update(t: T)
+
+    @Delete
+    fun delete(t: T)
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
new file mode 100644
index 0000000..1f8e992
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/BooksDao.kt
@@ -0,0 +1,143 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.dao
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Transaction
+import androidx.room.TypeConverters
+import androidx.room.integration.kotlintestapp.vo.Author
+import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.integration.kotlintestapp.vo.BookAuthor
+import androidx.room.integration.kotlintestapp.vo.BookWithPublisher
+import androidx.room.integration.kotlintestapp.vo.Lang
+import androidx.room.integration.kotlintestapp.vo.Publisher
+import androidx.room.integration.kotlintestapp.vo.PublisherWithBookSales
+import androidx.room.integration.kotlintestapp.vo.PublisherWithBooks
+import com.google.common.base.Optional
+import com.google.common.util.concurrent.ListenableFuture
+import io.reactivex.Flowable
+import io.reactivex.Maybe
+import io.reactivex.Single
+
+@Dao
+interface BooksDao {
+
+    @Insert
+    fun addPublishers(vararg publishers: Publisher)
+
+    @Delete
+    fun deletePublishers(vararg publishers: Publisher)
+
+    @Insert
+    fun addAuthors(vararg authors: Author)
+
+    @Query("SELECT * FROM author WHERE authorId = :authorId")
+    fun getAuthor(authorId: String): Author
+
+    @Insert
+    fun addBooks(vararg books: Book)
+
+    @Insert
+    fun addBookAuthors(vararg bookAuthors: BookAuthor)
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBook(bookId: String): Book
+
+    @Query("""SELECT * FROM book WHERE
+            bookId IN(:bookIds)
+            order by bookId DESC""")
+    fun getBooksMultiLineQuery(bookIds: List<String>): List<Book>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookLiveData(bookId: String): LiveData<Book>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookFlowable(bookId: String): Flowable<Book>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookJavaOptional(bookId: String): java.util.Optional<Book>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookListenableFuture(bookId: String): ListenableFuture<Book>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookOptional(bookId: String): Optional<Book>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookOptionalFlowable(bookId: String): Flowable<Optional<Book>>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookOptionalListenableFuture(bookId: String): ListenableFuture<Optional<Book>>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookSingle(bookId: String): Single<Book>
+
+    @Query("SELECT * FROM book WHERE bookId = :bookId")
+    fun getBookMaybe(bookId: String): Maybe<Book>
+
+    @Query("SELECT * FROM book INNER JOIN publisher " +
+            "ON book.bookPublisherId = publisher.publisherId ")
+    fun getBooksWithPublisher(): List<BookWithPublisher>
+
+    @Query("SELECT * FROM book INNER JOIN publisher " +
+            "ON book.bookPublisherId = publisher.publisherId ")
+    fun getBooksWithPublisherLiveData(): LiveData<List<BookWithPublisher>>
+
+    @Query("SELECT * FROM book INNER JOIN publisher " +
+            "ON book.bookPublisherId = publisher.publisherId ")
+    fun getBooksWithPublisherFlowable(): Flowable<List<BookWithPublisher>>
+
+    @Query("SELECT * FROM book INNER JOIN publisher " +
+            "ON book.bookPublisherId = publisher.publisherId ")
+    fun getBooksWithPublisherListenableFuture(): ListenableFuture<List<BookWithPublisher>>
+
+    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
+    fun getPublisherWithBooks(publisherId: String): PublisherWithBooks
+
+    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
+    fun getPublisherWithBookSales(publisherId: String): PublisherWithBookSales
+
+    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
+    fun getPublisherWithBooksLiveData(publisherId: String): LiveData<PublisherWithBooks>
+
+    @Query("SELECT * FROM publisher WHERE publisherId = :publisherId")
+    fun getPublisherWithBooksFlowable(publisherId: String): Flowable<PublisherWithBooks>
+
+    @Query("UPDATE book SET title = :title WHERE bookId = :bookId")
+    fun updateBookTitle(bookId: String, title: String?)
+
+    @Query("SELECT * FROM book WHERE languages & :langs != 0 ORDER BY bookId ASC")
+    @TypeConverters(Lang::class)
+    fun findByLanguages(langs: Set<Lang>): List<Book>
+
+    @Transaction
+    fun deleteAndAddPublisher(oldPublisher: Publisher, newPublisher: Publisher,
+            fail: Boolean = false) {
+        deletePublishers(oldPublisher)
+        if (fail) {
+            throw RuntimeException()
+        }
+        addPublishers(newPublisher)
+    }
+
+    @Query("SELECT * FROM Publisher")
+    fun getPublishers(): List<Publisher>
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/DerivedDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/DerivedDao.kt
new file mode 100644
index 0000000..851263c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/DerivedDao.kt
@@ -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 androidx.room.integration.kotlintestapp.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import androidx.room.integration.kotlintestapp.vo.Author
+
+@Dao
+interface DerivedDao : BaseDao<Author> {
+
+    @Query("SELECT * FROM author WHERE authorId = :authorId")
+    fun getAuthor(authorId: String): Author
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationDbKotlin.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationDbKotlin.kt
new file mode 100644
index 0000000..b65acd0
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationDbKotlin.kt
@@ -0,0 +1,128 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.migration
+
+import android.content.ContentValues
+import android.database.sqlite.SQLiteDatabase
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Ignore
+import androidx.room.Index
+import androidx.room.Insert
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.RoomDatabase
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+@Database(version = MigrationDbKotlin.LATEST_VERSION,
+        entities = arrayOf(MigrationDbKotlin.Entity1::class, MigrationDbKotlin.Entity2::class,
+                MigrationDbKotlin.Entity4::class))
+abstract class MigrationDbKotlin : RoomDatabase() {
+
+    internal abstract fun dao(): MigrationDao
+
+    @Entity(indices = arrayOf(Index(value = "name", unique = true)))
+    data class Entity1(@PrimaryKey var id: Int = 0, var name: String?) {
+
+        companion object {
+            val TABLE_NAME = "Entity1"
+        }
+    }
+
+    @Entity
+    open class Entity2(@PrimaryKey var id: Int = 0, var addedInV3: String?, var name: String?) {
+        companion object {
+            val TABLE_NAME = "Entity2"
+        }
+    }
+
+    @Entity
+    data class Entity3(@PrimaryKey var id: Int = 0, @Ignore var removedInV5: String?,
+                       var name: String?) { // added in version 4, removed at 6
+        companion object {
+            val TABLE_NAME = "Entity3"
+        }
+    }
+
+    @Entity(foreignKeys = arrayOf(ForeignKey(entity = Entity1::class,
+            parentColumns = arrayOf("name"),
+            childColumns = arrayOf("name"),
+            deferred = true)))
+    data class Entity4(@PrimaryKey var id: Int = 0, var name: String?) {
+        companion object {
+            val TABLE_NAME = "Entity4"
+        }
+    }
+
+    @Dao
+    internal interface MigrationDao {
+        @Query("SELECT * from Entity1 ORDER BY id ASC")
+        fun loadAllEntity1s(): List<Entity1>
+
+        @Query("SELECT * from Entity2 ORDER BY id ASC")
+        fun loadAllEntity2s(): List<Entity2>
+
+        @Query("SELECT * from Entity2 ORDER BY id ASC")
+        fun loadAllEntity2sAsPojo(): List<Entity2Pojo>
+
+        @Insert
+        fun insert(vararg entity2: Entity2)
+    }
+
+    internal class Entity2Pojo(id: Int, addedInV3: String?, name: String?)
+        : Entity2(id, addedInV3, name)
+
+    /**
+     * not a real dao because database will change.
+     */
+    internal class Dao_V1(val mDb: SupportSQLiteDatabase) {
+
+        fun insertIntoEntity1(id: Int, name: String) {
+            val values = ContentValues()
+            values.put("id", id)
+            values.put("name", name)
+            val insertionId = mDb.insert(Entity1.TABLE_NAME,
+                    SQLiteDatabase.CONFLICT_REPLACE, values)
+            if (insertionId == -1L) {
+                throw RuntimeException("test sanity failure")
+            }
+        }
+    }
+
+    /**
+     * not a real dao because database will change.
+     */
+    internal class Dao_V2(val mDb: SupportSQLiteDatabase) {
+
+        fun insertIntoEntity2(id: Int, name: String) {
+            val values = ContentValues()
+            values.put("id", id)
+            values.put("name", name)
+            val insertionId = mDb.insert(Entity2.TABLE_NAME,
+                    SQLiteDatabase.CONFLICT_REPLACE, values)
+            if (insertionId == -1L) {
+                throw RuntimeException("test sanity failure")
+            }
+        }
+    }
+
+    companion object {
+        const val LATEST_VERSION = 7
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
new file mode 100644
index 0000000..7185984
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
@@ -0,0 +1,305 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.migration
+
+import android.support.test.InstrumentationRegistry
+import androidx.room.Room
+import androidx.room.migration.Migration
+import androidx.room.testing.MigrationTestHelper
+import androidx.room.util.TableInfo
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.containsString
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Rule
+import org.junit.Test
+import java.io.FileNotFoundException
+import java.io.IOException
+
+class MigrationKotlinTest {
+
+    @get:Rule
+    var helper: MigrationTestHelper = MigrationTestHelper(
+            InstrumentationRegistry.getInstrumentation(),
+            MigrationDbKotlin::class.java.canonicalName,
+            FrameworkSQLiteOpenHelperFactory())
+
+    companion object {
+        val TEST_DB = "migration-test"
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun giveBadResource() {
+        val helper = MigrationTestHelper(
+                InstrumentationRegistry.getInstrumentation(),
+                "foo", FrameworkSQLiteOpenHelperFactory())
+        try {
+            helper.createDatabase(TEST_DB, 1)
+            throw AssertionError("must have failed with missing file exception")
+        } catch (exception: FileNotFoundException) {
+            assertThat<String>(exception.message, containsString("Cannot find"))
+        }
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun startInCurrentVersion() {
+        val db = helper.createDatabase(TEST_DB,
+                MigrationDbKotlin.LATEST_VERSION)
+        val dao = MigrationDbKotlin.Dao_V1(db)
+        dao.insertIntoEntity1(2, "x")
+        db.close()
+        val migrationDb = getLatestDb()
+        val items = migrationDb.dao().loadAllEntity1s()
+        helper.closeWhenFinished(migrationDb)
+        assertThat<Int>(items.size, `is`<Int>(1))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun addTable() {
+        var db = helper.createDatabase(TEST_DB, 1)
+        val dao = MigrationDbKotlin.Dao_V1(db)
+        dao.insertIntoEntity1(2, "foo")
+        dao.insertIntoEntity1(3, "bar")
+        db.close()
+        db = helper.runMigrationsAndValidate(TEST_DB, 2, true,
+                MIGRATION_1_2)
+        MigrationDbKotlin.Dao_V2(db).insertIntoEntity2(3, "blah")
+        db.close()
+        val migrationDb = getLatestDb()
+        val entity1s = migrationDb.dao().loadAllEntity1s()
+
+        assertThat(entity1s.size, `is`(2))
+        val entity2 = MigrationDbKotlin.Entity2(2, null, "bar")
+        // assert no error happens
+        migrationDb.dao().insert(entity2)
+        val entity2s = migrationDb.dao().loadAllEntity2s()
+        assertThat(entity2s.size, `is`(2))
+    }
+
+    private fun getLatestDb(): MigrationDbKotlin {
+        val db = Room.databaseBuilder(
+                InstrumentationRegistry.getInstrumentation().targetContext,
+                MigrationDbKotlin::class.java, TEST_DB).addMigrations(*ALL_MIGRATIONS).build()
+        // trigger open
+        db.beginTransaction()
+        db.endTransaction()
+        helper.closeWhenFinished(db)
+        return db
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun addTableFailure() {
+        testFailure(1, 2)
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun addColumnFailure() {
+        val db = helper.createDatabase(TEST_DB, 2)
+        db.close()
+        var caught: IllegalStateException? = null
+        try {
+            helper.runMigrationsAndValidate(TEST_DB, 3, true,
+                    EmptyMigration(2, 3))
+        } catch (ex: IllegalStateException) {
+            caught = ex
+        }
+
+        assertThat<IllegalStateException>(caught,
+                instanceOf<IllegalStateException>(IllegalStateException::class.java))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun addColumn() {
+        val db = helper.createDatabase(TEST_DB, 2)
+        val v2Dao = MigrationDbKotlin.Dao_V2(db)
+        v2Dao.insertIntoEntity2(7, "blah")
+        db.close()
+        helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3)
+        // trigger open.
+        val migrationDb = getLatestDb()
+        val entity2s = migrationDb.dao().loadAllEntity2s()
+        assertThat(entity2s.size, `is`(1))
+        assertThat<String>(entity2s[0].name, `is`("blah"))
+        assertThat<String>(entity2s[0].addedInV3, `is`<Any>(nullValue()))
+
+        val entity2Pojos = migrationDb.dao().loadAllEntity2sAsPojo()
+        assertThat(entity2Pojos.size, `is`(1))
+        assertThat<String>(entity2Pojos[0].name, `is`("blah"))
+        assertThat<String>(entity2Pojos[0].addedInV3, `is`<Any>(nullValue()))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun failedToRemoveColumn() {
+        testFailure(4, 5)
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun removeColumn() {
+        helper.createDatabase(TEST_DB, 4)
+        val db = helper.runMigrationsAndValidate(TEST_DB,
+                5, true, MIGRATION_4_5)
+        val info = TableInfo.read(db, MigrationDbKotlin.Entity3.TABLE_NAME)
+        assertThat(info.columns.size, `is`(2))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun dropTable() {
+        helper.createDatabase(TEST_DB, 5)
+        val db = helper.runMigrationsAndValidate(TEST_DB,
+                6, true, MIGRATION_5_6)
+        val info = TableInfo.read(db, MigrationDbKotlin.Entity3.TABLE_NAME)
+        assertThat(info.columns.size, `is`(0))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun failedToDropTable() {
+        testFailure(5, 6)
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun failedToDropTableDontVerify() {
+        helper.createDatabase(TEST_DB, 5)
+        val db = helper.runMigrationsAndValidate(TEST_DB,
+                6, false, EmptyMigration(5, 6))
+        val info = TableInfo.read(db, MigrationDbKotlin.Entity3.TABLE_NAME)
+        assertThat(info.columns.size, `is`(2))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun failedForeignKey() {
+        val db = helper.createDatabase(TEST_DB, 6)
+        db.close()
+        var throwable: Throwable? = null
+        try {
+            helper.runMigrationsAndValidate(TEST_DB,
+                    7, false, object : Migration(6, 7) {
+                override fun migrate(database: SupportSQLiteDatabase) {
+                    database.execSQL("CREATE TABLE Entity4 (`id` INTEGER, `name` TEXT,"
+                            + " PRIMARY KEY(`id`))")
+                }
+            })
+        } catch (t: Throwable) {
+            throwable = t
+        }
+
+        assertThat<Throwable>(throwable, instanceOf<Throwable>(IllegalStateException::class.java))
+
+        assertThat<String>(throwable!!.message, containsString("Migration failed"))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun newTableWithForeignKey() {
+        helper.createDatabase(TEST_DB, 6)
+        val db = helper.runMigrationsAndValidate(TEST_DB,
+                7, false, MIGRATION_6_7)
+        val info = TableInfo.read(db, MigrationDbKotlin.Entity4.TABLE_NAME)
+        assertThat(info.foreignKeys.size, `is`(1))
+    }
+
+    @Throws(IOException::class)
+    private fun testFailure(startVersion: Int, endVersion: Int) {
+        val db = helper.createDatabase(TEST_DB, startVersion)
+        db.close()
+        var throwable: Throwable? = null
+        try {
+            helper.runMigrationsAndValidate(TEST_DB, endVersion, true,
+                    EmptyMigration(startVersion, endVersion))
+        } catch (t: Throwable) {
+            throwable = t
+        }
+
+        assertThat<Throwable>(throwable, instanceOf<Throwable>(IllegalStateException::class.java))
+        assertThat<String>(throwable!!.message, containsString("Migration failed"))
+    }
+
+    internal val MIGRATION_1_2: Migration = object : Migration(1, 2) {
+        override fun migrate(database: SupportSQLiteDatabase) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity2` (`id` INTEGER NOT NULL,"
+                    + " `name` TEXT, PRIMARY KEY(`id`))")
+        }
+    }
+
+    internal val MIGRATION_2_3: Migration = object : Migration(2, 3) {
+        override fun migrate(database: SupportSQLiteDatabase) {
+            database.execSQL("ALTER TABLE " + MigrationDbKotlin.Entity2.TABLE_NAME
+                    + " ADD COLUMN addedInV3 TEXT")
+        }
+    }
+
+    internal val MIGRATION_3_4: Migration = object : Migration(3, 4) {
+        override fun migrate(database: SupportSQLiteDatabase) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3` (`id` INTEGER NOT NULL,"
+                    + " `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))")
+        }
+    }
+
+    internal val MIGRATION_4_5: Migration = object : Migration(4, 5) {
+        override fun migrate(database: SupportSQLiteDatabase) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3_New` (`id` INTEGER NOT NULL,"
+                    + " `name` TEXT, PRIMARY KEY(`id`))")
+            database.execSQL("INSERT INTO Entity3_New(`id`, `name`) "
+                    + "SELECT `id`, `name` FROM Entity3")
+            database.execSQL("DROP TABLE Entity3")
+            database.execSQL("ALTER TABLE Entity3_New RENAME TO Entity3")
+        }
+    }
+
+    internal val MIGRATION_5_6: Migration = object : Migration(5, 6) {
+        override fun migrate(database: SupportSQLiteDatabase) {
+            database.execSQL("DROP TABLE " + MigrationDbKotlin.Entity3.TABLE_NAME)
+        }
+    }
+
+    internal val MIGRATION_6_7: Migration = object : Migration(6, 7) {
+        override fun migrate(database: SupportSQLiteDatabase) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS "
+                    + MigrationDbKotlin.Entity4.TABLE_NAME
+                    + " (`id` INTEGER NOT NULL, `name` TEXT, 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 "
+                    + MigrationDbKotlin.Entity1.TABLE_NAME + " (`name`)")
+        }
+    }
+
+    private val ALL_MIGRATIONS = arrayOf(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
+            MIGRATION_5_6, MIGRATION_6_7)
+
+    internal class EmptyMigration(startVersion: Int, endVersion: Int)
+        : Migration(startVersion, endVersion) {
+
+        override fun migrate(database: SupportSQLiteDatabase) {
+            // do nothing
+        }
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/BooksDaoTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/BooksDaoTest.kt
new file mode 100644
index 0000000..d73bc96
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/BooksDaoTest.kt
@@ -0,0 +1,323 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.test
+
+import android.database.sqlite.SQLiteConstraintException
+import android.support.test.filters.SmallTest
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.room.integration.kotlintestapp.vo.Author
+import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.integration.kotlintestapp.vo.BookWithPublisher
+import androidx.room.integration.kotlintestapp.vo.Lang
+import androidx.room.integration.kotlintestapp.vo.Publisher
+import com.google.common.base.Optional
+import io.reactivex.Flowable
+import io.reactivex.schedulers.Schedulers
+import io.reactivex.subscribers.TestSubscriber
+import org.hamcrest.CoreMatchers
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Assert.assertNotNull
+import org.junit.Test
+import java.util.Date
+
+@SmallTest
+class BooksDaoTest : TestDatabaseTest() {
+
+    @Test
+    fun bookById() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        assertThat(booksDao.getBook(TestUtil.BOOK_1.bookId), `is`<Book>(TestUtil.BOOK_1))
+    }
+
+    @Test
+    fun bookByIdJavaOptional() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        assertThat(
+                booksDao.getBookJavaOptional(TestUtil.BOOK_1.bookId),
+                `is`<java.util.Optional<Book>>(java.util.Optional.of(TestUtil.BOOK_1)))
+    }
+
+    @Test
+    fun bookByIdJavaOptionalEmpty() {
+        assertThat(
+                booksDao.getBookJavaOptional(TestUtil.BOOK_1.bookId),
+                `is`<java.util.Optional<Book>>(java.util.Optional.empty()))
+    }
+
+    @Test
+    fun bookByIdListenableFuture() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        assertThat(
+                booksDao.getBookListenableFuture(TestUtil.BOOK_1.bookId).get(),
+                `is`<Book>(TestUtil.BOOK_1))
+    }
+
+    @Test
+    fun bookByIdOptional() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        assertThat(
+                booksDao.getBookOptional(TestUtil.BOOK_1.bookId),
+                `is`<Optional<Book>>(Optional.of(TestUtil.BOOK_1)))
+    }
+
+    @Test
+    fun bookByIdOptionalListenableFuture() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        assertThat(
+                booksDao.getBookOptionalListenableFuture(TestUtil.BOOK_1.bookId).get(),
+                `is`<Optional<Book>>(Optional.of(TestUtil.BOOK_1)))
+    }
+
+    @Test
+    fun bookByIdOptionalListenableFutureAbsent() {
+        assertThat(
+                booksDao.getBookOptionalListenableFuture(TestUtil.BOOK_1.bookId).get(),
+                `is`<Optional<Book>>(Optional.absent()))
+    }
+
+    @Test
+    fun bookByIdOptionalAbsent() {
+        assertThat(
+                booksDao.getBookOptional(TestUtil.BOOK_1.bookId),
+                `is`<Optional<Book>>(Optional.absent()))
+    }
+
+    @Test
+    fun bookByIdOptionalFlowable() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        val subscriber = TestSubscriber<Optional<Book>>()
+        val flowable: Flowable<Optional<Book>> =
+                booksDao.getBookOptionalFlowable(TestUtil.BOOK_1.bookId)
+        flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(subscriber)
+
+        assertThat(subscriber.values().size, `is`(1))
+        assertThat(subscriber.values()[0], `is`(Optional.of(TestUtil.BOOK_1)))
+    }
+
+    @Test
+    fun bookByIdOptionalFlowableAbsent() {
+        val subscriber = TestSubscriber<Optional<Book>>()
+        val flowable: Flowable<Optional<Book>> =
+                booksDao.getBookOptionalFlowable(TestUtil.BOOK_1.bookId)
+        flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(subscriber)
+
+        assertThat(subscriber.values().size, `is`(1))
+        assertThat(subscriber.values()[0], `is`(Optional.absent()))
+    }
+
+    @Test
+    fun bookWithPublisher() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        val expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
+                TestUtil.PUBLISHER)
+        val expectedList = ArrayList<BookWithPublisher>()
+        expectedList.add(expected)
+
+        assertThat(database.booksDao().getBooksWithPublisher(),
+                `is`<List<BookWithPublisher>>(expectedList))
+    }
+
+    @Test
+    fun bookWithPublisherListenableFuture() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        val expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
+                TestUtil.PUBLISHER)
+        val expectedList = ArrayList<BookWithPublisher>()
+        expectedList.add(expected)
+
+        assertThat(database.booksDao().getBooksWithPublisherListenableFuture().get(),
+                `is`<List<BookWithPublisher>>(expectedList))
+    }
+
+    @Test
+    fun updateBookWithNullTitle() {
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        var throwable: Throwable? = null
+        try {
+            booksDao.updateBookTitle(TestUtil.BOOK_1.bookId, null)
+        } catch (t: Throwable) {
+            throwable = t
+        }
+        assertNotNull(throwable)
+        assertThat<Throwable>(throwable, instanceOf<Throwable>(SQLiteConstraintException::class
+                .java))
+    }
+
+    @Test
+    fun publisherWithBooks() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+        val actualPublisherWithBooks = booksDao.getPublisherWithBooks(
+                TestUtil.PUBLISHER.publisherId)
+
+        assertThat(actualPublisherWithBooks.publisher, `is`<Publisher>(TestUtil.PUBLISHER))
+        assertThat(actualPublisherWithBooks.books?.size, `is`(2))
+        assertThat(actualPublisherWithBooks.books?.get(0), `is`<Book>(TestUtil.BOOK_1))
+        assertThat(actualPublisherWithBooks.books?.get(1), `is`<Book>(TestUtil.BOOK_2))
+    }
+
+    @Test // b/68077506
+    fun publisherWithBookSales() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+        val actualPublisherWithBooks = booksDao.getPublisherWithBookSales(
+                TestUtil.PUBLISHER.publisherId)
+
+        assertThat(actualPublisherWithBooks.publisher, `is`<Publisher>(TestUtil.PUBLISHER))
+        assertThat(actualPublisherWithBooks.sales, `is`(listOf(TestUtil.BOOK_1.salesCnt,
+                TestUtil.BOOK_2.salesCnt)))
+    }
+
+    @Test
+    fun insertAuthorWithAllFields() {
+        val author = Author("id", "name", Date(), ArrayList())
+        database.booksDao().addAuthors(author)
+
+        val authorDb = database.booksDao().getAuthor(author.authorId)
+
+        assertThat(authorDb, CoreMatchers.`is`<Author>(author))
+    }
+
+    @Test
+    fun insertInInheritedDao() {
+        database.derivedDao().insert(TestUtil.AUTHOR_1)
+
+        val author = database.derivedDao().getAuthor(TestUtil.AUTHOR_1.authorId)
+
+        assertThat(author, CoreMatchers.`is`<Author>(TestUtil.AUTHOR_1))
+    }
+
+    @Test
+    fun findBooksInMultiLineQuery() {
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+        booksDao.addBooks(TestUtil.BOOK_2)
+
+        val books = database.booksDao().getBooksMultiLineQuery(arrayListOf(
+                TestUtil.BOOK_1.bookId,
+                TestUtil.BOOK_2.bookId))
+        assertThat(books, `is`(listOf(TestUtil.BOOK_2, TestUtil.BOOK_1)))
+    }
+
+    @Test
+    fun findBooksByLanguage() {
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        val book1 = TestUtil.BOOK_1.copy(languages = setOf(Lang.TR))
+        val book2 = TestUtil.BOOK_2.copy(languages = setOf(Lang.ES, Lang.TR))
+        val book3 = TestUtil.BOOK_3.copy(languages = setOf(Lang.EN))
+        booksDao.addBooks(book1, book2, book3)
+
+        assertThat(booksDao.findByLanguages(setOf(Lang.EN, Lang.TR)),
+                `is`(listOf(book1, book2, book3)))
+
+        assertThat(booksDao.findByLanguages(setOf(Lang.TR)),
+                `is`(listOf(book1, book2)))
+
+        assertThat(booksDao.findByLanguages(setOf(Lang.ES)),
+                `is`(listOf(book2)))
+
+        assertThat(booksDao.findByLanguages(setOf(Lang.EN)),
+                `is`(listOf(book3)))
+    }
+
+    @Test
+    fun insertVarargInInheritedDao() {
+        database.derivedDao().insertAllArg(TestUtil.AUTHOR_1, TestUtil.AUTHOR_2)
+
+        val author = database.derivedDao().getAuthor(TestUtil.AUTHOR_1.authorId)
+
+        assertThat(author, CoreMatchers.`is`<Author>(TestUtil.AUTHOR_1))
+    }
+
+    @Test
+    fun insertListInInheritedDao() {
+        database.derivedDao().insertAll(listOf(TestUtil.AUTHOR_1))
+
+        val author = database.derivedDao().getAuthor(TestUtil.AUTHOR_1.authorId)
+
+        assertThat(author, CoreMatchers.`is`<Author>(TestUtil.AUTHOR_1))
+    }
+
+    @Test
+    fun deleteAndAddPublisher() {
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.getPublishers().run {
+            assertThat(this.size, `is`(1))
+            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER)))
+        }
+        booksDao.deleteAndAddPublisher(TestUtil.PUBLISHER, TestUtil.PUBLISHER2)
+        booksDao.getPublishers().run {
+            assertThat(this.size, `is`(1))
+            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER2)))
+        }
+    }
+
+    @Test
+    fun deleteAndAddPublisher_failure() {
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.getPublishers().run {
+            assertThat(this.size, `is`(1))
+            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER)))
+        }
+        var throwable: Throwable? = null
+        try {
+            booksDao.deleteAndAddPublisher(TestUtil.PUBLISHER, TestUtil.PUBLISHER2, true)
+        } catch (e: RuntimeException) {
+            throwable = e
+        }
+        assertThat(throwable, `is`(notNullValue()))
+        booksDao.getPublishers().run {
+            assertThat(this.size, `is`(1))
+            assertThat(this.first(), `is`(equalTo(TestUtil.PUBLISHER)))
+        }
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/DependencyDaoTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/DependencyDaoTest.kt
new file mode 100644
index 0000000..300695f
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/DependencyDaoTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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 androidx.room.integration.kotlintestapp.test
+
+import android.os.Build
+import android.support.test.filters.SdkSuppress
+import android.support.test.runner.AndroidJUnit4
+import androidx.room.integration.kotlintestapp.dao.DependencyDao
+import androidx.room.integration.kotlintestapp.vo.DataClassFromDependency
+import androidx.room.integration.kotlintestapp.vo.EmbeddedFromDependency
+import androidx.room.integration.kotlintestapp.vo.PojoFromDependency
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.CoreMatchers.nullValue
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DependencyDaoTest : TestDatabaseTest() {
+    lateinit var dao: DependencyDao
+    @Before
+    fun init() {
+        dao = database.dependencyDao()
+    }
+
+    @Test
+    fun insertAndGet() {
+        val data = insertSample(3)
+        assertThat(dao.selectAll(), `is`(listOf(data)))
+    }
+
+    @Test
+    fun insertAndGetByQuery() {
+        val data = insertSample(3)
+        assertThat(dao.findById(3), `is`(data))
+        assertThat(dao.findById(5), `is`(nullValue()))
+    }
+
+    @Test
+    fun insertAndGetByQuery_embedded() {
+        val data = insertSample(3)
+        assertThat(dao.findEmbedded(3), `is`(EmbeddedFromDependency(data)))
+        assertThat(dao.findEmbedded(5), `is`(nullValue()))
+    }
+
+    @Test
+    fun insertAndGetByQuery_pojo() {
+        val data = insertSample(3)
+        assertThat(dao.findPojo(3), `is`(PojoFromDependency(
+                id = data.id,
+                name = data.name)))
+        assertThat(dao.findPojo(5), `is`(nullValue()))
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    fun getRelation() {
+        val foo1 = DataClassFromDependency(
+                id = 3,
+                name = "foo"
+        )
+        val foo2 = DataClassFromDependency(
+                id = 4,
+                name = "foo"
+        )
+        val bar = DataClassFromDependency(
+                id = 5,
+                name = "bar"
+        )
+        dao.insert(foo1, foo2, bar)
+        val fooList = dao.relation("foo")
+        assertThat(fooList.sharedName, `is`("foo"))
+        assertThat(fooList, `is`(notNullValue()))
+        assertThat(fooList.dataItems, `is`(listOf(foo1, foo2)))
+
+        val barList = dao.relation("bar")
+        assertThat(barList.sharedName, `is`("bar"))
+        assertThat(barList, `is`(notNullValue()))
+        assertThat(barList.dataItems, `is`(listOf(bar)))
+
+        val bazList = dao.relation("baz")
+        assertThat(bazList.sharedName, `is`("baz"))
+        assertThat(bazList, `is`(notNullValue()))
+        assertThat(bazList.dataItems, `is`(emptyList()))
+    }
+
+    private fun insertSample(id: Int): DataClassFromDependency {
+        val data = DataClassFromDependency(
+                id = id,
+                name = "foo"
+        )
+        dao.insert(data)
+        return data
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/ItemWithNullableConstructor.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/ItemWithNullableConstructor.kt
new file mode 100644
index 0000000..d5b787c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/ItemWithNullableConstructor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 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 androidx.room.integration.kotlintestapp.test
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.runner.AndroidJUnit4
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.Insert
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.RoomWarnings
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ItemWithNullableConstructor {
+    lateinit var db: Db
+    @Before
+    fun initDb() {
+        db = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                Db::class.java).build()
+    }
+
+    @After
+    fun closeDb() {
+        db.close()
+    }
+
+    @Test
+    fun insertWithNull() {
+        db.dao.insert(TestItem(null, null))
+        assertThat(db.dao.get(), `is`(TestItem(1, null)))
+    }
+
+    @Entity
+    data class TestItem(
+            @PrimaryKey(autoGenerate = true)
+            val id: Long? = null,
+            val nullable: Boolean?
+    )
+
+    @Dao
+    interface TestDao {
+        @Insert
+        fun insert(testItem: TestItem)
+
+        @Query("SELECT * FROM TestItem LIMIT 1")
+        fun get(): TestItem?
+    }
+
+    @Database(
+            version = 1,
+            entities = [TestItem::class],
+            exportSchema = false
+    )
+    @SuppressWarnings(RoomWarnings.MISSING_SCHEMA_LOCATION)
+    abstract class Db : RoomDatabase() {
+        abstract val dao: TestDao
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/LiveDataQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/LiveDataQueryTest.kt
new file mode 100644
index 0000000..f39cea5
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/LiveDataQueryTest.kt
@@ -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 androidx.room.integration.kotlintestapp.test
+
+import android.support.test.filters.SmallTest
+import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.integration.kotlintestapp.vo.BookWithPublisher
+import androidx.room.integration.kotlintestapp.vo.Publisher
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+
+@SmallTest
+class LiveDataQueryTest : TestDatabaseTest() {
+
+    @Test
+    fun observeBooksById() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        val book = LiveDataTestUtil.getValue(booksDao.getBookLiveData(TestUtil.BOOK_1.bookId))
+
+        assertThat(book, `is`<Book>(TestUtil.BOOK_1))
+    }
+
+    @Test
+    fun observeBooksWithPublisher() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        var expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
+                TestUtil.PUBLISHER)
+        var expectedList = ArrayList<BookWithPublisher>()
+        expectedList.add(expected)
+
+        val actual = LiveDataTestUtil.getValue(booksDao.getBooksWithPublisherLiveData())
+        assertThat(actual, `is`<List<BookWithPublisher>>(expectedList))
+    }
+
+    @Test
+    fun publisherWithBooks() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+        var actualPublisherWithBooks = LiveDataTestUtil.getValue(
+                booksDao.getPublisherWithBooksLiveData(TestUtil.PUBLISHER.publisherId))
+
+        assertThat(actualPublisherWithBooks.publisher, `is`<Publisher>(TestUtil.PUBLISHER))
+        assertThat(actualPublisherWithBooks.books?.size, `is`(2))
+        assertThat(actualPublisherWithBooks.books?.get(0), `is`<Book>(TestUtil.BOOK_1))
+        assertThat(actualPublisherWithBooks.books?.get(1), `is`<Book>(TestUtil.BOOK_2))
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/LiveDataTestUtil.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/LiveDataTestUtil.kt
new file mode 100644
index 0000000..6dbd2b4
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/LiveDataTestUtil.kt
@@ -0,0 +1,37 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.test
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+
+object LiveDataTestUtil {
+
+    @Throws(InterruptedException::class)
+    fun <T> getValue(liveData: LiveData<T>): T {
+        val data = arrayOfNulls<Any>(1)
+        val observer = object : Observer<T> {
+            override fun onChanged(o: T?) {
+                data[0] = o
+                liveData.removeObserver(this)
+            }
+        }
+        liveData.observeForever(observer)
+
+        return data[0] as T
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/RxJava2QueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/RxJava2QueryTest.kt
new file mode 100644
index 0000000..f62dc70
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/RxJava2QueryTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.test
+
+import android.support.test.filters.SmallTest
+import androidx.room.EmptyResultSetException
+import androidx.room.integration.kotlintestapp.vo.BookWithPublisher
+import org.junit.Test
+
+@SmallTest
+class RxJava2QueryTest : TestDatabaseTest() {
+
+    @Test
+    fun observeBooksById() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        booksDao.getBookFlowable(TestUtil.BOOK_1.bookId)
+                .test()
+                .assertValue { book -> book == TestUtil.BOOK_1 }
+    }
+
+    @Test
+    fun observeBooksByIdSingle() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        booksDao.getBookSingle(TestUtil.BOOK_1.bookId)
+                .test()
+                .assertComplete()
+                .assertValue { book -> book == TestUtil.BOOK_1 }
+    }
+
+    @Test
+    fun observeBooksByIdSingle_noBook() {
+        booksDao.getBookSingle("x")
+                .test()
+                .assertError(EmptyResultSetException::class.java)
+    }
+
+    @Test
+    fun observeBooksByIdMaybe() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        booksDao.getBookMaybe(TestUtil.BOOK_1.bookId)
+                .test()
+                .assertComplete()
+                .assertValue { book -> book == TestUtil.BOOK_1 }
+    }
+
+    @Test
+    fun observeBooksByIdMaybe_noBook() {
+        booksDao.getBookMaybe("x")
+                .test()
+                .assertComplete()
+                .assertNoErrors()
+                .assertNoValues()
+    }
+
+    @Test
+    fun observeBooksWithPublisher() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        var expected = BookWithPublisher(TestUtil.BOOK_1.bookId, TestUtil.BOOK_1.title,
+                TestUtil.PUBLISHER)
+        var expectedList = ArrayList<BookWithPublisher>()
+        expectedList.add(expected)
+
+        booksDao.getBooksWithPublisherFlowable()
+                .test()
+                .assertValue(expectedList)
+    }
+
+    @Test
+    fun publisherWithBooks() {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+        booksDao.getPublisherWithBooksFlowable(TestUtil.PUBLISHER.publisherId)
+                .test()
+                .assertValue {
+                    it.publisher == TestUtil.PUBLISHER
+                            && it.books?.size == 2
+                            && it.books?.get(0) == TestUtil.BOOK_1
+                            && it.books?.get(1) == TestUtil.BOOK_2
+                }
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
new file mode 100644
index 0000000..0f213188
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.test
+
+import android.support.test.InstrumentationRegistry
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.room.Room
+import androidx.room.integration.kotlintestapp.TestDatabase
+import androidx.room.integration.kotlintestapp.dao.BooksDao
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+
+abstract class TestDatabaseTest {
+
+    @get:Rule
+    var instantTaskExecutorRule = InstantTaskExecutorRule()
+
+    protected lateinit var database: TestDatabase
+    protected lateinit var booksDao: BooksDao
+
+    @Before
+    @Throws(Exception::class)
+    fun setUp() {
+        database = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+                TestDatabase::class.java)
+                // allowing main thread queries, just for testing
+                .allowMainThreadQueries()
+                .build()
+
+        booksDao = database.booksDao()
+    }
+
+    @After
+    @Throws(Exception::class)
+    fun tearDown() {
+        database.close()
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestUtil.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestUtil.kt
new file mode 100644
index 0000000..dfb4841
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestUtil.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.test
+
+import androidx.room.integration.kotlintestapp.vo.Author
+import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.integration.kotlintestapp.vo.BookAuthor
+import androidx.room.integration.kotlintestapp.vo.Lang
+import androidx.room.integration.kotlintestapp.vo.Publisher
+
+class TestUtil {
+
+    companion object {
+
+        val PUBLISHER = Publisher("ph1", "publisher 1")
+        val PUBLISHER2 = Publisher("ph2", "publisher 2")
+
+        val AUTHOR_1 = Author("a1", "author 1")
+        val AUTHOR_2 = Author("a2", "author 2")
+
+        val BOOK_1 = Book("b1", "book title 1", "ph1",
+                setOf(Lang.EN), 3)
+        val BOOK_2 = Book("b2", "book title 2", "ph1",
+                setOf(Lang.TR), 5)
+        val BOOK_3 = Book("b3", "book title 2", "ph1",
+                setOf(Lang.ES), 7)
+
+        val BOOK_AUTHOR_1_1 = BookAuthor(BOOK_1.bookId, AUTHOR_1.authorId)
+        val BOOK_AUTHOR_1_2 = BookAuthor(BOOK_1.bookId, AUTHOR_2.authorId)
+        val BOOK_AUTHOR_2_2 = BookAuthor(BOOK_2.bookId, AUTHOR_2.authorId)
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Author.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Author.kt
new file mode 100644
index 0000000..6ee662d
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Author.kt
@@ -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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.TypeConverters
+import java.util.Date
+
+@Entity
+@TypeConverters(DateConverter::class, StringToIntListConverters::class)
+data class Author(
+        @PrimaryKey val authorId: String,
+        val name: String,
+        val dateOfBirth: Date? = null,
+        val aList: List<Integer>? = null)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Book.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Book.kt
new file mode 100644
index 0000000..7b0def9
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Book.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.PrimaryKey
+import androidx.room.TypeConverters
+
+@Entity(foreignKeys = arrayOf(
+        ForeignKey(entity = Publisher::class,
+                parentColumns = arrayOf("publisherId"),
+                childColumns = arrayOf("bookPublisherId"),
+                deferred = true)))
+data class Book(
+        @PrimaryKey val bookId: String,
+        val title: String,
+        val bookPublisherId: String,
+        @field:TypeConverters(Lang::class)
+        val languages: Set<Lang>,
+        val salesCnt: Int)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/BookAuthor.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/BookAuthor.kt
new file mode 100644
index 0000000..0201429
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/BookAuthor.kt
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.ForeignKey
+
+@Entity(foreignKeys = arrayOf(
+        ForeignKey(entity = Book::class,
+                parentColumns = arrayOf("bookId"),
+                childColumns = arrayOf("bookId"),
+                onUpdate = ForeignKey.CASCADE,
+                onDelete = ForeignKey.CASCADE,
+                deferred = true),
+        ForeignKey(entity = Author::class,
+                parentColumns = arrayOf("authorId"),
+                childColumns = arrayOf("authorId"),
+                onUpdate = ForeignKey.CASCADE,
+                onDelete = ForeignKey.CASCADE,
+                deferred = true)),
+        primaryKeys = arrayOf("bookId", "authorId"))
+data class BookAuthor(val bookId: String, val authorId: String)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/BookWithPublisher.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/BookWithPublisher.kt
new file mode 100644
index 0000000..069a894
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/BookWithPublisher.kt
@@ -0,0 +1,22 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Embedded
+
+data class BookWithPublisher(val bookId: String, val title: String,
+                             @Embedded val publisher: Publisher)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/DateConverter.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/DateConverter.kt
new file mode 100755
index 0000000..cfdeb87
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/DateConverter.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.TypeConverter
+
+import java.util.Date
+
+class DateConverter {
+    @TypeConverter
+    fun toDate(timestamp: Long?): Date? {
+        return if (timestamp == null) null else Date(timestamp)
+    }
+
+    @TypeConverter
+    fun toTimestamp(date: Date?): Long? {
+        return date?.time
+    }
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Lang.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Lang.kt
new file mode 100644
index 0000000..473f566
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Lang.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.TypeConverter
+
+/**
+ * An enum class which gets saved as a bit set in the database.
+ */
+enum class Lang {
+    TR,
+    EN,
+    ES;
+
+    companion object {
+        @JvmStatic
+        @TypeConverter
+        fun toInt(langs: Set<Lang>): Int {
+            return langs.fold(0) { left, lang ->
+                left.or(1 shl lang.ordinal)
+            }
+        }
+
+        @JvmStatic
+        @TypeConverter
+        fun toSet(value: Int): Set<Lang> {
+            return Lang.values().filter {
+                (1 shl it.ordinal).and(value) != 0
+            }.toSet()
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/NoArgClass.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/NoArgClass.kt
new file mode 100644
index 0000000..335de5b
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/NoArgClass.kt
@@ -0,0 +1,26 @@
+/*
+ * 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+/**
+ * just here to ensure that we handle no-arg constructors fine from kotlin.
+ */
+@Entity
+data class NoArgClass(@PrimaryKey var id: Long = 0, var class_name: String = "")
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Publisher.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Publisher.kt
new file mode 100644
index 0000000..c38a823
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Publisher.kt
@@ -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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Publisher(@PrimaryKey val publisherId: String, val name: String)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PublisherWithBookSales.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PublisherWithBookSales.kt
new file mode 100644
index 0000000..4b1fdde
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PublisherWithBookSales.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Embedded
+import androidx.room.Relation
+
+data class PublisherWithBookSales @JvmOverloads constructor(
+        @Embedded
+        val publisher: Publisher,
+        @Relation(parentColumn = "publisherId", // publisher.publisherId
+                entityColumn = "bookPublisherId", // book.bookPublisherId
+                entity = Book::class,
+                projection = ["salesCnt"])
+        var sales: List<Int>? = emptyList())
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PublisherWithBooks.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PublisherWithBooks.kt
new file mode 100644
index 0000000..8a6e0d1
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PublisherWithBooks.kt
@@ -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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Embedded
+import androidx.room.Relation
+
+class PublisherWithBooks {
+    @Embedded var publisher: Publisher? = null
+    @Relation(parentColumn = "publisherId", // publisher.publisherId
+            entityColumn = "bookPublisherId", // book.bookPublisherId
+            entity = Book::class)
+    var books: List<Book>? = null
+}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/StringToIntListConverters.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/StringToIntListConverters.kt
new file mode 100644
index 0000000..38f552a
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/StringToIntListConverters.kt
@@ -0,0 +1,18 @@
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.TypeConverter
+import androidx.room.util.StringUtil
+
+object StringToIntListConverters {
+    @TypeConverter
+    // Specifying that a static method should be generated. Otherwise, the compiler looks for the
+    // constructor of the class, and a object has a private constructor.
+    @JvmStatic
+    fun stringToIntList(data: String?): List<Int>? =
+            if (data == null) null else StringUtil.splitToIntList(data)
+
+    @TypeConverter
+    @JvmStatic
+    fun intListToString(ints: List<Int>?): String? =
+            if (ints == null) null else StringUtil.joinIntoString(ints)
+}
diff --git a/room/integration-tests/kotlintestapp/src/main/AndroidManifest.xml b/room/integration-tests/kotlintestapp/src/main/AndroidManifest.xml
index d079891..690eeb5 100644
--- a/room/integration-tests/kotlintestapp/src/main/AndroidManifest.xml
+++ b/room/integration-tests/kotlintestapp/src/main/AndroidManifest.xml
@@ -15,4 +15,4 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.arch.persistence.room.integration.kotlintestapp"/>
+    package="androidx.room.integration.kotlintestapp"/>
diff --git a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/dao/DependencyDao.kt b/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/dao/DependencyDao.kt
deleted file mode 100644
index 90e45cf..0000000
--- a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/dao/DependencyDao.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2018 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.kotlintestapp.dao
-
-import android.arch.persistence.room.Dao
-import android.arch.persistence.room.Insert
-import android.arch.persistence.room.OnConflictStrategy
-import android.arch.persistence.room.Query
-import android.arch.persistence.room.integration.kotlintestapp.vo.DataClassFromDependency
-import android.arch.persistence.room.integration.kotlintestapp.vo.EmbeddedFromDependency
-import android.arch.persistence.room.integration.kotlintestapp.vo.PojoFromDependency
-import android.arch.persistence.room.integration.kotlintestapp.vo.RelationFromDependency
-import android.os.Build
-import android.support.annotation.RequiresApi
-
-@Dao
-interface DependencyDao {
-    @Query("select * from DataClassFromDependency")
-    fun selectAll(): List<DataClassFromDependency>
-
-    @Query("select * from DataClassFromDependency where id = :id LIMIT 1")
-    fun findEmbedded(id: Int): EmbeddedFromDependency
-
-    @Query("select * from DataClassFromDependency where id = :id LIMIT 1")
-    fun findPojo(id: Int): PojoFromDependency
-
-    @Query("select * from DataClassFromDependency where id = :id LIMIT 1")
-    fun findById(id: Int): DataClassFromDependency
-
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-    @Query("WITH nameTable( sharedName ) AS ( SELECT :name ) SELECT * from nameTable")
-    fun relation(name: String): RelationFromDependency
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    fun insert(vararg input: DataClassFromDependency)
-}
diff --git a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/DataClassFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/DataClassFromDependency.kt
deleted file mode 100644
index 258ada6..0000000
--- a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/DataClassFromDependency.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2018 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.kotlintestapp.vo
-
-import android.arch.persistence.room.Entity
-import android.arch.persistence.room.PrimaryKey
-
-/**
- * used to test the case where kotlin classes from dependencies cannot be read properly.
- * Since the main db in this app is in the test module, the original classes serve as a dependency.
- */
-@Entity
-data class DataClassFromDependency(
-        @PrimaryKey(autoGenerate = true)
-        val id: Int,
-        val name: String)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/EmbeddedFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/EmbeddedFromDependency.kt
deleted file mode 100644
index e1ed9be..0000000
--- a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/EmbeddedFromDependency.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2018 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.kotlintestapp.vo
-
-import android.arch.persistence.room.Embedded
-
-data class EmbeddedFromDependency(
-        @Embedded
-        val data: DataClassFromDependency)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/PojoFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/PojoFromDependency.kt
deleted file mode 100644
index f2cc3ad..0000000
--- a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/PojoFromDependency.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2018 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.kotlintestapp.vo
-
-data class PojoFromDependency(
-        val id: Int,
-        val name: String)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/RelationFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/RelationFromDependency.kt
deleted file mode 100644
index 5fe7d4b..0000000
--- a/room/integration-tests/kotlintestapp/src/main/java/android/arch/persistence/room/integration/kotlintestapp/vo/RelationFromDependency.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2018 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.kotlintestapp.vo
-
-import android.arch.persistence.room.Relation
-
-class RelationFromDependency(val sharedName: String) {
-    @Relation(
-            parentColumn = "sharedName",
-            entityColumn = "name"
-    )
-    lateinit var dataItems: List<DataClassFromDependency>
-}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/dao/DependencyDao.kt b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/dao/DependencyDao.kt
new file mode 100644
index 0000000..e5e8eb6
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/dao/DependencyDao.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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 androidx.room.integration.kotlintestapp.dao
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.integration.kotlintestapp.vo.DataClassFromDependency
+import androidx.room.integration.kotlintestapp.vo.EmbeddedFromDependency
+import androidx.room.integration.kotlintestapp.vo.PojoFromDependency
+import androidx.room.integration.kotlintestapp.vo.RelationFromDependency
+
+@Dao
+interface DependencyDao {
+    @Query("select * from DataClassFromDependency")
+    fun selectAll(): List<DataClassFromDependency>
+
+    @Query("select * from DataClassFromDependency where id = :id LIMIT 1")
+    fun findEmbedded(id: Int): EmbeddedFromDependency
+
+    @Query("select * from DataClassFromDependency where id = :id LIMIT 1")
+    fun findPojo(id: Int): PojoFromDependency
+
+    @Query("select * from DataClassFromDependency where id = :id LIMIT 1")
+    fun findById(id: Int): DataClassFromDependency
+
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    @Query("WITH nameTable( sharedName ) AS ( SELECT :name ) SELECT * from nameTable")
+    fun relation(name: String): RelationFromDependency
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insert(vararg input: DataClassFromDependency)
+}
diff --git a/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/DataClassFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/DataClassFromDependency.kt
new file mode 100644
index 0000000..6dd8307
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/DataClassFromDependency.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+/**
+ * used to test the case where kotlin classes from dependencies cannot be read properly.
+ * Since the main db in this app is in the test module, the original classes serve as a dependency.
+ */
+@Entity
+data class DataClassFromDependency(
+        @PrimaryKey(autoGenerate = true)
+        val id: Int,
+        val name: String)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/EmbeddedFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/EmbeddedFromDependency.kt
new file mode 100644
index 0000000..2d1c46e
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/EmbeddedFromDependency.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Embedded
+
+data class EmbeddedFromDependency(
+        @Embedded
+        val data: DataClassFromDependency)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/PojoFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/PojoFromDependency.kt
new file mode 100644
index 0000000..fee9838
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/PojoFromDependency.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.kotlintestapp.vo
+
+data class PojoFromDependency(
+        val id: Int,
+        val name: String)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/RelationFromDependency.kt b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/RelationFromDependency.kt
new file mode 100644
index 0000000..8f0b48b
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/main/java/androidx/room/integration/kotlintestapp/vo/RelationFromDependency.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Relation
+
+class RelationFromDependency(val sharedName: String) {
+    @Relation(
+            parentColumn = "sharedName",
+            entityColumn = "name"
+    )
+    lateinit var dataItems: List<DataClassFromDependency>
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 401cc07..22fd802 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -34,30 +34,30 @@
 }
 
 dependencies {
-    implementation(project(":room:common"))
-    implementation(project(":persistence:db"))
-    implementation(project(":persistence:db-framework"))
-    implementation(project(":room:runtime"))
-    implementation(project(":arch:runtime"))
-    implementation(project(":arch:common"))
-    implementation(project(":paging:common"))
-    implementation(project(":lifecycle:extensions"))
-    implementation(project(":lifecycle:runtime"))
-    implementation(project(":lifecycle:common"))
-    implementation(project(":room:rxjava2"))
-    implementation(project(":paging:runtime"))
+    implementation(project(":room:room-common"))
+    implementation(project(":sqlite:sqlite"))
+    implementation(project(":sqlite:sqlite-framework"))
+    implementation(project(":room:room-runtime"))
+    implementation(project(":arch:core-runtime"))
+    implementation(project(":arch:core-common"))
+    implementation(project(":paging:paging-common"))
+    implementation(project(":lifecycle:lifecycle-extensions"))
+    implementation(project(":lifecycle:lifecycle-runtime"))
+    implementation(project(":lifecycle:lifecycle-common"))
+    implementation(project(":room:room-rxjava2"))
+    implementation(project(":paging:paging-runtime"))
 
-    implementation(SUPPORT_RECYCLERVIEW_27, libs.support_exclude_config)
-    implementation(SUPPORT_APPCOMPAT_27, libs.support_exclude_config)
-    annotationProcessor project(":room:compiler")
-    androidTestAnnotationProcessor project(":room:compiler")
+    implementation(SUPPORT_RECYCLERVIEW, libs.support_exclude_config)
+    implementation(SUPPORT_APPCOMPAT, libs.support_exclude_config)
+    annotationProcessor project(":room:room-compiler")
+    androidTestAnnotationProcessor project(":room:room-compiler")
 
     // IJ's gradle integration just cannot figure this out ...
-    androidTestImplementation(project(":lifecycle:extensions"))
-    androidTestImplementation(project(":lifecycle:common"))
-    androidTestImplementation(project(":lifecycle:runtime"))
-    androidTestImplementation(project(":room:testing"))
-    androidTestImplementation(project(":room:rxjava2"))
+    androidTestImplementation(project(":lifecycle:lifecycle-extensions"))
+    androidTestImplementation(project(":lifecycle:lifecycle-common"))
+    androidTestImplementation(project(":lifecycle:lifecycle-runtime"))
+    androidTestImplementation(project(":room:room-testing"))
+    androidTestImplementation(project(":room:room-rxjava2"))
     androidTestImplementation(project(":arch:core-testing"))
     androidTestImplementation(RX_JAVA)
     androidTestImplementation(TEST_RUNNER)
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/1.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/1.json
similarity index 100%
rename from room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/1.json
rename to room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/1.json
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/2.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/2.json
similarity index 100%
rename from room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/2.json
rename to room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/2.json
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/3.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/3.json
similarity index 100%
rename from room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/3.json
rename to room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/3.json
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/4.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/4.json
similarity index 100%
rename from room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/4.json
rename to room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/4.json
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/5.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/5.json
similarity index 100%
rename from room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/5.json
rename to room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/5.json
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/6.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/6.json
similarity index 100%
rename from room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/6.json
rename to room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/6.json
diff --git a/room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/7.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/7.json
similarity index 100%
rename from room/integration-tests/testapp/schemas/android.arch.persistence.room.integration.testapp.migration.MigrationDb/7.json
rename to room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.MigrationDb/7.json
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/InvalidationTrackerTrojan.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/InvalidationTrackerTrojan.java
deleted file mode 100644
index 5fa15ac..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/InvalidationTrackerTrojan.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-/**
- * Trojan class to be able to assert internal state.
- */
-public class InvalidationTrackerTrojan {
-    public static int countObservers(InvalidationTracker tracker) {
-        return tracker.mObserverMap.size();
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
deleted file mode 100644
index 63b9507..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
-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,
-        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
-    public interface IntPKeyDao {
-        @Insert
-        void insertMe(IntAutoIncPKeyEntity... items);
-        @Insert
-        long insertAndGetId(IntAutoIncPKeyEntity item);
-
-        @Insert
-        long[] insertAndGetIds(IntAutoIncPKeyEntity... item);
-
-        @Query("select * from IntAutoIncPKeyEntity WHERE pKey = :key")
-        IntAutoIncPKeyEntity getMe(int key);
-
-        @Query("select data from IntAutoIncPKeyEntity WHERE pKey IN(:ids)")
-        List<String> loadDataById(long... ids);
-    }
-
-    @Dao
-    public interface IntegerAutoIncPKeyDao {
-        @Insert
-        void insertMe(IntegerAutoIncPKeyEntity item);
-
-        @Query("select * from IntegerAutoIncPKeyEntity WHERE pKey = :key")
-        IntegerAutoIncPKeyEntity getMe(int key);
-
-        @Insert
-        long insertAndGetId(IntegerAutoIncPKeyEntity item);
-
-        @Insert
-        long[] insertAndGetIds(IntegerAutoIncPKeyEntity... item);
-
-        @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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/TestDatabase.java
deleted file mode 100644
index 98282ab..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/TestDatabase.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp;
-
-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 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;
-import android.arch.persistence.room.integration.testapp.dao.RawDao;
-import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
-import android.arch.persistence.room.integration.testapp.dao.SpecificDogDao;
-import android.arch.persistence.room.integration.testapp.dao.ToyDao;
-import android.arch.persistence.room.integration.testapp.dao.UserDao;
-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.Day;
-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;
-import android.arch.persistence.room.integration.testapp.vo.School;
-import android.arch.persistence.room.integration.testapp.vo.Toy;
-import android.arch.persistence.room.integration.testapp.vo.User;
-
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
-
-@Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
-        BlobEntity.class, Product.class, FunnyNamedEntity.class},
-        version = 1, exportSchema = false)
-@TypeConverters(TestDatabase.Converters.class)
-public abstract class TestDatabase extends RoomDatabase {
-    public abstract UserDao getUserDao();
-    public abstract PetDao getPetDao();
-    public abstract UserPetDao getUserPetDao();
-    public abstract SchoolDao getSchoolDao();
-    public abstract PetCoupleDao getPetCoupleDao();
-    public abstract ToyDao getToyDao();
-    public abstract BlobEntityDao getBlobEntityDao();
-    public abstract ProductDao getProductDao();
-    public abstract SpecificDogDao getSpecificDogDao();
-    public abstract WithClauseDao getWithClauseDao();
-    public abstract FunnyNamedDao getFunnyNamedDao();
-    public abstract RawDao getRawDao();
-
-    @SuppressWarnings("unused")
-    public static class Converters {
-        @TypeConverter
-        public Date fromTimestamp(Long value) {
-            return value == null ? null : new Date(value);
-        }
-
-        @TypeConverter
-        public Long dateToTimestamp(Date date) {
-            if (date == null) {
-                return null;
-            } else {
-                return date.getTime();
-            }
-        }
-
-        @TypeConverter
-        public Set<Day> decomposeDays(int flags) {
-            Set<Day> result = new HashSet<>();
-            for (Day day : Day.values()) {
-                if ((flags & (1 << day.ordinal())) != 0) {
-                    result.add(day);
-                }
-            }
-            return result;
-        }
-
-        @TypeConverter
-        public int composeDays(Set<Day> days) {
-            int result = 0;
-            for (Day day : days) {
-                result |= 1 << day.ordinal();
-            }
-            return result;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/BlobEntityDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/BlobEntityDao.java
deleted file mode 100644
index 212eba7..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/BlobEntityDao.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
-
-import java.util.List;
-
-@Dao
-public interface BlobEntityDao {
-
-    @Insert
-    void insert(BlobEntity item);
-
-    @Query("SELECT * FROM BlobEntity")
-    List<BlobEntity> selectAll();
-
-    @Query("SELECT content FROM BlobEntity WHERE id = :id")
-    byte[] getContent(long id);
-
-    @Query("UPDATE BlobEntity SET content = :content WHERE id = :id")
-    void updateContent(long id, byte[] content);
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
deleted file mode 100644
index 93b5e72..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetCoupleDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetCoupleDao.java
deleted file mode 100644
index 4f7c4e2..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetCoupleDao.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.integration.testapp.vo.PetCouple;
-
-import java.util.List;
-
-@Dao
-public interface PetCoupleDao {
-    @Insert
-    void insert(PetCouple couple);
-
-    @Query("SELECT * FROM PetCouple")
-    List<PetCouple> loadAll();
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetDao.java
deleted file mode 100644
index e3a45a0..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/PetDao.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Delete;
-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.integration.testapp.vo.Pet;
-import android.arch.persistence.room.integration.testapp.vo.PetWithToyIds;
-
-import java.util.List;
-
-@Dao
-public interface PetDao {
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    void insertOrReplace(Pet... pets);
-
-    @Insert
-    void insertAll(Pet[] pets);
-
-    @Query("SELECT COUNT(*) FROM Pet")
-    int count();
-
-    @Query("SELECT * FROM Pet ORDER BY Pet.mPetId ASC")
-    List<PetWithToyIds> allPetsWithToyIds();
-
-    @Delete
-    void delete(Pet pet);
-
-    @Query("SELECT mPetId FROM Pet")
-    int[] allIds();
-
-    @Transaction
-    default void deleteAndInsert(Pet oldPet, Pet newPet, boolean shouldFail) {
-        delete(oldPet);
-        if (shouldFail) {
-            throw new RuntimeException();
-        }
-        insertOrReplace(newPet);
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ProductDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ProductDao.java
deleted file mode 100644
index 417fb72..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ProductDao.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.integration.testapp.vo.Product;
-import android.support.annotation.NonNull;
-
-@Dao
-public interface ProductDao {
-
-    @Insert
-    long insert(@NonNull Product product);
-
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
deleted file mode 100644
index ae09350..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/RawDao.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2018 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 android.arch.lifecycle.LiveData;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.RawQuery;
-import android.arch.persistence.room.integration.testapp.vo.NameAndLastName;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
-import android.arch.persistence.room.integration.testapp.vo.UserAndPet;
-
-import java.util.Date;
-import java.util.List;
-
-@Dao
-public interface RawDao {
-    @RawQuery
-    User getUser(String query);
-
-    @RawQuery
-    UserAndAllPets getUserAndAllPets(String query);
-
-    @RawQuery(observedEntities = UserAndAllPets.class)
-    LiveData<UserAndAllPets> getUserAndAllPetsObservable(String query);
-
-    @RawQuery
-    User getUser(SupportSQLiteQuery query);
-
-    @RawQuery
-    UserAndPet getUserAndPet(String query);
-
-    @RawQuery
-    NameAndLastName getUserNameAndLastName(String query);
-
-    @RawQuery(observedEntities = User.class)
-    NameAndLastName getUserNameAndLastName(SupportSQLiteQuery query);
-
-    @RawQuery
-    int count(String query);
-
-    @RawQuery
-    List<User> getUserList(String query);
-
-    @RawQuery
-    List<UserAndPet> getUserAndPetList(String query);
-
-    @RawQuery(observedEntities = UserAndPet.class)
-    LiveData<List<UserAndPet>> getUserAndPetListObservable(String query);
-
-    @RawQuery(observedEntities = User.class)
-    LiveData<User> getUserLiveData(String query);
-
-    @RawQuery(observedEntities = User.class)
-    LiveData<User> getUserLiveData(SupportSQLiteQuery query);
-
-    @RawQuery
-    UserNameAndBirthday getUserAndBirthday(String query);
-
-    class UserNameAndBirthday {
-        @ColumnInfo(name = "mName")
-        public final String name;
-        @ColumnInfo(name = "mBirthday")
-        public final Date birthday;
-
-        public UserNameAndBirthday(String name, Date birthday) {
-            this.name = name;
-            this.birthday = birthday;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
deleted file mode 100644
index 18e8d93..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.OnConflictStrategy;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.RoomWarnings;
-import android.arch.persistence.room.integration.testapp.vo.Coordinates;
-import android.arch.persistence.room.integration.testapp.vo.School;
-import android.arch.persistence.room.integration.testapp.vo.SchoolRef;
-
-import java.util.List;
-
-@Dao
-public abstract class SchoolDao {
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    public abstract void insert(School... schools);
-
-    @Query("SELECT * from School WHERE address_street LIKE '%' || :street || '%'")
-    public abstract List<School> findByStreet(String street);
-
-    @Query("SELECT mId, mName, manager_mName FROM School")
-    public abstract List<School> schoolAndManagerNames();
-
-    @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 mId, address_lat, address_lng FROM School WHERE mId = :schoolId")
-    public abstract School loadCoordinatesAsSchool(int schoolId);
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SpecificDogDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SpecificDogDao.java
deleted file mode 100644
index 971826d..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/SpecificDogDao.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.lifecycle.LiveData;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.integration.testapp.vo.PetsToys;
-
-@Dao
-public interface SpecificDogDao {
-    @Query("SELECT 123 as petId")
-    LiveData<PetsToys> getSpecificDogsToys();
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ToyDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ToyDao.java
deleted file mode 100644
index 5d50a62..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/ToyDao.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.integration.testapp.vo.Toy;
-
-@Dao
-public interface ToyDao {
-    @Insert
-    void insert(Toy... toys);
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
deleted file mode 100644
index 1db5b46..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.lifecycle.LiveData;
-import android.arch.paging.DataSource;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Delete;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.OnConflictStrategy;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.RawQuery;
-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;
-import android.arch.persistence.room.integration.testapp.vo.Day;
-import android.arch.persistence.room.integration.testapp.vo.NameAndLastName;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.database.Cursor;
-
-import org.reactivestreams.Publisher;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import io.reactivex.Flowable;
-import io.reactivex.Maybe;
-import io.reactivex.Single;
-
-@SuppressWarnings("SameParameterValue")
-@Dao
-public abstract class UserDao {
-
-    private final TestDatabase mDatabase;
-
-    public UserDao(TestDatabase database) {
-        mDatabase = database;
-    }
-
-    @Query("select * from user where mName like :name")
-    public abstract List<User> findUsersByName(String name);
-
-    @Query("select * from user where mId = :id")
-    public abstract User load(int id);
-
-    @Query("select * from user where mId IN(:ids)")
-    public abstract User[] loadByIds(int... ids);
-
-    @Query("select * from user where custommm = :customField")
-    public abstract List<User> findByCustomField(String customField);
-
-    @Insert
-    public abstract void insert(User user);
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    public abstract void insertOrReplace(User user);
-
-    @Delete
-    public abstract int delete(User user);
-
-    @Delete
-    public abstract int deleteAll(User[] users);
-
-    @Query("delete from user")
-    public abstract int deleteEverything();
-
-    @Update
-    public abstract int update(User user);
-
-    @Update
-    public abstract int updateAll(List<User> users);
-
-    @Insert
-    public abstract void insertAll(User[] users);
-
-    @Query("select * from user where mAdmin = :isAdmin")
-    public abstract List<User> findByAdmin(boolean isAdmin);
-
-    @Query("delete from user where mAge > :age")
-    public abstract int deleteAgeGreaterThan(int age);
-
-    @Query("delete from user where mId IN(:uids)")
-    public abstract int deleteByUids(int... uids);
-
-    @Query("delete from user where mAge >= :min AND mAge <= :max")
-    public abstract int deleteByAgeRange(int min, int max);
-
-    @Query("update user set mName = :name where mId = :id")
-    public abstract int updateById(int id, String name);
-
-    @Query("update user set mId = mId + :amount")
-    public abstract void incrementIds(int amount);
-
-    @Query("update user set mAge = mAge + 1")
-    public abstract void incrementAgeOfAll();
-
-    @Query("select mId from user order by mId ASC")
-    public abstract List<Integer> loadIds();
-
-    @Query("select * from user where mId = :id")
-    public abstract LiveData<User> liveUserById(int id);
-
-    @Transaction
-    @Query("select * from user where mId = :id")
-    public abstract LiveData<User> liveUserByIdInTransaction(int id);
-
-    @Query("select * from user where mName LIKE '%' || :name || '%' ORDER BY mId DESC")
-    public abstract LiveData<List<User>> liveUsersListByName(String name);
-
-    @Query("select * from user where length(mName) = :length")
-    public abstract List<User> findByNameLength(int length);
-
-    @Query("select * from user where mAge = :age")
-    public abstract List<User> findByAge(int age);
-
-    @Query("select mAge, AVG(mWeight) from user GROUP BY mAge ORDER BY 2 DESC")
-    public abstract List<AvgWeightByAge> weightByAge();
-
-    @Query("select mAge, AVG(mWeight) from user GROUP BY mAge ORDER BY 2 DESC LIMIT 1")
-    public abstract LiveData<AvgWeightByAge> maxWeightByAgeGroup();
-
-    @Query("select * from user where mBirthday > :from AND mBirthday < :to")
-    public abstract List<User> findByBirthdayRange(Date from, Date to);
-
-    @Query("select mId from user where mId IN (:ids)")
-    public abstract Cursor findUsersAsCursor(int... ids);
-
-    @Query("select * from user where mId = :id")
-    public abstract Flowable<User> flowableUserById(int id);
-
-    @Query("select * from user where mId = :id")
-    public abstract Maybe<User> maybeUserById(int id);
-
-    @Query("select * from user where mId IN (:ids)")
-    public abstract Maybe<List<User>> maybeUsersByIds(int... ids);
-
-    @Query("select * from user where mId = :id")
-    public abstract Single<User> singleUserById(int id);
-
-    @Query("select * from user where mId IN (:ids)")
-    public abstract Single<List<User>> singleUsersByIds(int... ids);
-
-    @Query("select COUNT(*) from user")
-    public abstract Flowable<Integer> flowableCountUsers();
-
-    @Query("select COUNT(*) from user")
-    public abstract Publisher<Integer> publisherCountUsers();
-
-    @Query("SELECT mBirthday from User where mId = :id")
-    public abstract Date getBirthday(int id);
-
-    @Query("SELECT COUNT(*) from user")
-    public abstract int count();
-
-    @Query("SELECT mAdmin from User where mId = :uid")
-    public abstract boolean isAdmin(int uid);
-
-    @Query("SELECT mAdmin from User where mId = :uid")
-    public abstract LiveData<Boolean> isAdminLiveData(int uid);
-
-    public void insertBothByRunnable(final User a, final User b) {
-        mDatabase.runInTransaction(new Runnable() {
-            @Override
-            public void run() {
-                insert(a);
-                insert(b);
-            }
-        });
-    }
-
-    public int insertBothByCallable(final User a, final User b) {
-        return mDatabase.runInTransaction(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                insert(a);
-                insert(b);
-                return 2;
-            }
-        });
-    }
-
-    @Query("SELECT * FROM user where mAge > :age")
-    public abstract DataSource.Factory<Integer, User> loadPagedByAge(int age);
-
-    @RawQuery(observedEntities = User.class)
-    public abstract DataSource.Factory<Integer, User> loadPagedByAgeWithObserver(
-            SupportSQLiteQuery query);
-
-    // TODO: switch to PositionalDataSource once Room supports it
-    @Query("SELECT * FROM user ORDER BY mAge DESC")
-    public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
-
-    @Query("DELETE FROM User WHERE mId IN (:ids) AND mAge == :age")
-    public abstract int deleteByAgeAndIds(int age, List<Integer> ids);
-
-    @Query("UPDATE User set mWeight = :weight WHERE mId IN (:ids) AND mAge == :age")
-    public abstract int updateByAgeAndIds(float weight, int age, List<Integer> ids);
-
-    @Query("SELECT * FROM user WHERE (mWorkDays & :days) != 0")
-    public abstract List<User> findUsersByWorkDays(Set<Day> days);
-
-    // QueryLoader
-
-    @Query("SELECT COUNT(*) from user")
-    public abstract Integer getUserCount();
-
-    @Query("SELECT u.mName, u.mLastName from user u where mId = :id")
-    public abstract NameAndLastName getNameAndLastName(int id);
-
-    @Transaction
-    public void insertBothByAnnotation(final User a, final User b) {
-        insert(a);
-        insert(b);
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
deleted file mode 100644
index d7a0e1e..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.dao;
-
-import android.arch.lifecycle.LiveData;
-import android.arch.paging.DataSource;
-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.EmbeddedUserAndAllPets;
-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.arch.persistence.room.integration.testapp.vo.UserAndPet;
-import android.arch.persistence.room.integration.testapp.vo.UserAndPetAdoptionDates;
-import android.arch.persistence.room.integration.testapp.vo.UserAndPetNonNull;
-import android.arch.persistence.room.integration.testapp.vo.UserIdAndPetNames;
-import android.arch.persistence.room.integration.testapp.vo.UserWithPetsAndToys;
-
-import java.util.List;
-
-import io.reactivex.Flowable;
-
-@Dao
-public interface UserPetDao {
-    @Query("SELECT * FROM User u, Pet p WHERE u.mId = p.mUserId")
-    List<UserAndPet> loadAll();
-
-    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
-    List<UserAndPet> loadUsers();
-
-    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
-    List<UserAndPetNonNull> loadUsersWithNonNullPet();
-
-    @Query("SELECT * FROM Pet p LEFT OUTER JOIN User u ON u.mId = p.mUserId")
-    List<UserAndPet> loadPets();
-
-    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
-    DataSource.Factory<Integer, UserAndAllPets> dataSourceFactoryMultiTable();
-
-    @Query("SELECT * FROM User u")
-    List<UserAndAllPets> loadAllUsersWithTheirPets();
-
-    @Query("SELECT * FROM User u")
-    List<UserIdAndPetNames> loadUserAndPetNames();
-
-    @Query("SELECT * FROM User u")
-    List<UserWithPetsAndToys> loadUserWithPetsAndToys();
-
-    @Query("SELECT * FROM User UNION ALL SELECT * FROM USER")
-    List<UserAndAllPets> unionByItself();
-
-    @Query("SELECT * FROM User")
-    List<UserAndPetAdoptionDates> loadUserWithPetAdoptionDates();
-
-    @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);
-
-    @Insert
-    void insertUserAndPet(User user, Pet pet);
-
-    @Update
-    void updateUsersAndPets(User[] users, Pet[] pets);
-
-    @Delete
-    void delete2UsersAndPets(User user1, User user2, Pet[] pets);
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
deleted file mode 100644
index 40098ed..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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"
-            + "   UNION ALL \n"
-            + " SELECT n+1, (n+1)*fact FROM factorial \n"
-            + "   WHERE n < :maxIndexInclusive)\n"
-            + " SELECT fact FROM factorial;")
-    List<Integer> getFactorials(int maxIndexInclusive);
-
-    @Query("WITH RECURSIVE factorial(n, fact) AS \n"
-            + "(SELECT 0, 1 \n"
-            + "   UNION ALL \n"
-            + " SELECT n+1, (n+1)*fact FROM factorial \n"
-            + "   WHERE n < :maxIndexInclusive)\n"
-            + " SELECT mName FROM User WHERE User.mId IN (Select fact FROM factorial);")
-    List<String> getUsersWithFactorialIds(int maxIndexInclusive);
-
-    @Query("WITH RECURSIVE factorial(n, fact) AS \n"
-            + "(SELECT 0, 1 \n"
-            + "   UNION ALL \n"
-            + " SELECT n+1, (n+1)*fact FROM factorial \n"
-            + "   WHERE n < :maxIndexInclusive)\n"
-            + " SELECT mName FROM User WHERE User.mId IN (Select fact FROM factorial);")
-    LiveData<List<String>> getUsersWithFactorialIdsLiveData(int maxIndexInclusive);
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
deleted file mode 100644
index ef207cf..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.migration;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.Ignore;
-import android.arch.persistence.room.Index;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.RoomDatabase;
-import android.content.ContentValues;
-import android.database.sqlite.SQLiteDatabase;
-
-import java.util.List;
-
-@SuppressWarnings("WeakerAccess")
-@Database(version = MigrationDb.LATEST_VERSION,
-        entities = {MigrationDb.Entity1.class, MigrationDb.Entity2.class,
-                MigrationDb.Entity4.class})
-public abstract class MigrationDb extends RoomDatabase {
-    static final int LATEST_VERSION = 7;
-    abstract MigrationDao dao();
-    @Entity(indices = {@Index(value = "name", unique = true)})
-    static class Entity1 {
-        public static final String TABLE_NAME = "Entity1";
-        @PrimaryKey
-        public int id;
-        public String name;
-    }
-
-    @Entity
-    static class Entity2 {
-        public static final String TABLE_NAME = "Entity2";
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public String addedInV3;
-        public String name;
-    }
-
-    @Entity
-    static class Entity3 { // added in version 4, removed at 6
-        public static final String TABLE_NAME = "Entity3";
-        @PrimaryKey
-        public int id;
-        @Ignore //removed at 5
-        public String removedInV5;
-        public String name;
-    }
-
-    @Entity(foreignKeys = {
-            @ForeignKey(entity = Entity1.class,
-            parentColumns = "name",
-            childColumns = "name",
-            deferred = true)})
-    static class Entity4 {
-        public static final String TABLE_NAME = "Entity4";
-        @PrimaryKey
-        public int id;
-        @ColumnInfo(collate = ColumnInfo.NOCASE)
-        public String name;
-    }
-
-    @Dao
-    interface MigrationDao {
-        @Query("SELECT * from Entity1 ORDER BY id ASC")
-        List<Entity1> loadAllEntity1s();
-        @Query("SELECT * from Entity2 ORDER BY id ASC")
-        List<Entity2> loadAllEntity2s();
-        @Query("SELECT * from Entity2 ORDER BY id ASC")
-        List<Entity2Pojo> loadAllEntity2sAsPojo();
-        @Insert
-        void insert(Entity2... entity2);
-    }
-
-    static class Entity2Pojo extends Entity2 {
-    }
-
-    /**
-     * not a real dao because database will change.
-     */
-    static class Dao_V1 {
-        final SupportSQLiteDatabase mDb;
-
-        Dao_V1(SupportSQLiteDatabase db) {
-            mDb = db;
-        }
-
-        public void insertIntoEntity1(int id, String name) {
-            ContentValues values = new ContentValues();
-            values.put("id", id);
-            values.put("name", name);
-            long insertionId = mDb.insert(Entity1.TABLE_NAME,
-                    SQLiteDatabase.CONFLICT_REPLACE, values);
-            if (insertionId == -1) {
-                throw new RuntimeException("test sanity failure");
-            }
-        }
-    }
-
-    /**
-     * not a real dao because database will change.
-     */
-    static class Dao_V2 {
-        final SupportSQLiteDatabase mDb;
-
-        Dao_V2(SupportSQLiteDatabase db) {
-            mDb = db;
-        }
-
-        public void insertIntoEntity2(int id, String name) {
-            ContentValues values = new ContentValues();
-            values.put("id", id);
-            values.put("name", name);
-            long insertionId = mDb.insert(Entity2.TABLE_NAME,
-                    SQLiteDatabase.CONFLICT_REPLACE, values);
-            if (insertionId == -1) {
-                throw new RuntimeException("test sanity failure");
-            }
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
deleted file mode 100644
index c850a4d..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.migration;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.endsWith;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.CoreMatchers.startsWith;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.migration.Migration;
-import android.arch.persistence.room.testing.MigrationTestHelper;
-import android.arch.persistence.room.util.TableInfo;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.hamcrest.MatcherAssert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Test custom database migrations.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MigrationTest {
-    private static final String TEST_DB = "migration-test";
-    @Rule
-    public MigrationTestHelper helper;
-
-    public MigrationTest() {
-        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
-                MigrationDb.class.getCanonicalName());
-    }
-
-    @Test
-    public void giveBadResource() throws IOException {
-        MigrationTestHelper helper = new MigrationTestHelper(
-                InstrumentationRegistry.getInstrumentation(),
-                "foo", new FrameworkSQLiteOpenHelperFactory());
-        try {
-            helper.createDatabase(TEST_DB, 1);
-            throw new AssertionError("must have failed with missing file exception");
-        } catch (FileNotFoundException exception) {
-            assertThat(exception.getMessage(), containsString("Cannot find"));
-        }
-    }
-
-    @Test
-    public void startInCurrentVersion() throws IOException {
-        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB,
-                MigrationDb.LATEST_VERSION);
-        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
-        dao.insertIntoEntity1(2, "x");
-        db.close();
-        MigrationDb migrationDb = getLatestDb();
-        List<MigrationDb.Entity1> items = migrationDb.dao().loadAllEntity1s();
-        helper.closeWhenFinished(migrationDb);
-        assertThat(items.size(), is(1));
-    }
-
-    @Test
-    public void addTable() throws IOException {
-        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
-        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
-        dao.insertIntoEntity1(2, "foo");
-        dao.insertIntoEntity1(3, "bar");
-        db.close();
-        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
-        new MigrationDb.Dao_V2(db).insertIntoEntity2(3, "blah");
-        db.close();
-        MigrationDb migrationDb = getLatestDb();
-        List<MigrationDb.Entity1> entity1s = migrationDb.dao().loadAllEntity1s();
-
-        assertThat(entity1s.size(), is(2));
-        MigrationDb.Entity2 entity2 = new MigrationDb.Entity2();
-        entity2.id = 2;
-        entity2.name = "bar";
-        // assert no error happens
-        migrationDb.dao().insert(entity2);
-        List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
-        assertThat(entity2s.size(), is(2));
-    }
-
-    private MigrationDb getLatestDb() {
-        MigrationDb db = Room.databaseBuilder(
-                InstrumentationRegistry.getInstrumentation().getTargetContext(),
-                MigrationDb.class, TEST_DB).addMigrations(ALL_MIGRATIONS).build();
-        // trigger open
-        db.beginTransaction();
-        db.endTransaction();
-        helper.closeWhenFinished(db);
-        return db;
-    }
-
-    @Test
-    public void addTableFailure() throws IOException {
-        testFailure(1, 2);
-    }
-
-    @Test
-    public void addColumnFailure() throws IOException {
-        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
-        db.close();
-        IllegalStateException caught = null;
-        try {
-            helper.runMigrationsAndValidate(TEST_DB, 3, true, new EmptyMigration(2, 3));
-        } catch (IllegalStateException ex) {
-            caught = ex;
-        }
-        assertThat(caught, instanceOf(IllegalStateException.class));
-    }
-
-    @Test
-    public void addColumn() throws IOException {
-        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
-        MigrationDb.Dao_V2 v2Dao = new MigrationDb.Dao_V2(db);
-        v2Dao.insertIntoEntity2(7, "blah");
-        db.close();
-        helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3);
-        // trigger open.
-        MigrationDb migrationDb = getLatestDb();
-        List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
-        assertThat(entity2s.size(), is(1));
-        assertThat(entity2s.get(0).name, is("blah"));
-        assertThat(entity2s.get(0).addedInV3, is(nullValue()));
-
-        List<MigrationDb.Entity2Pojo> entity2Pojos = migrationDb.dao().loadAllEntity2sAsPojo();
-        assertThat(entity2Pojos.size(), is(1));
-        assertThat(entity2Pojos.get(0).name, is("blah"));
-        assertThat(entity2Pojos.get(0).addedInV3, is(nullValue()));
-    }
-
-    @Test
-    public void failedToRemoveColumn() throws IOException {
-        testFailure(4, 5);
-    }
-
-    @Test
-    public void removeColumn() throws IOException {
-        helper.createDatabase(TEST_DB, 4);
-        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
-                5, true, MIGRATION_4_5);
-        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
-        assertThat(info.columns.size(), is(2));
-    }
-
-    @Test
-    public void dropTable() throws IOException {
-        helper.createDatabase(TEST_DB, 5);
-        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
-                6, true, MIGRATION_5_6);
-        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
-        assertThat(info.columns.size(), is(0));
-    }
-
-    @Test
-    public void failedToDropTable() throws IOException {
-        testFailure(5, 6);
-    }
-
-    @Test
-    public void failedToDropTableDontVerify() throws IOException {
-        helper.createDatabase(TEST_DB, 5);
-        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
-                6, false, new EmptyMigration(5, 6));
-        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
-        assertThat(info.columns.size(), is(2));
-    }
-
-    @Test
-    public void failedForeignKey() throws IOException {
-        final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 6);
-        db.close();
-        Throwable throwable = null;
-        try {
-            helper.runMigrationsAndValidate(TEST_DB,
-                    7, false, new Migration(6, 7) {
-                        @Override
-                        public void migrate(SupportSQLiteDatabase database) {
-                            database.execSQL("CREATE TABLE Entity4 (`id` INTEGER NOT NULL,"
-                                    + " `name` TEXT, PRIMARY KEY(`id`))");
-                        }
-                    });
-        } catch (Throwable t) {
-            throwable = t;
-        }
-        assertThat(throwable, instanceOf(IllegalStateException.class));
-        //noinspection ConstantConditions
-        assertThat(throwable.getMessage(), containsString("Migration failed"));
-    }
-
-    @Test
-    public void newTableWithForeignKey() throws IOException {
-        helper.createDatabase(TEST_DB, 6);
-        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
-                7, false, MIGRATION_6_7);
-        final TableInfo info = TableInfo.read(db, MigrationDb.Entity4.TABLE_NAME);
-        assertThat(info.foreignKeys.size(), is(1));
-    }
-
-    @Test
-    public void missingMigration() throws IOException {
-        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
-        database.close();
-        try {
-            Context targetContext = InstrumentationRegistry.getTargetContext();
-            MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
-                    .build();
-            db.dao().loadAllEntity1s();
-            throw new AssertionError("Should've failed :/");
-        } catch (IllegalStateException ignored) {
-        }
-    }
-
-    @Test
-    public void missingMigrationNuke() throws IOException {
-        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
-        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(database);
-        dao.insertIntoEntity1(2, "foo");
-        dao.insertIntoEntity1(3, "bar");
-        database.close();
-
-        Context targetContext = InstrumentationRegistry.getTargetContext();
-        MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
-                .fallbackToDestructiveMigration()
-                .build();
-        assertThat(db.dao().loadAllEntity1s().size(), is(0));
-        db.close();
-    }
-
-    @Test
-    public void failWithIdentityCheck() throws IOException {
-        for (int i = 1; i < MigrationDb.LATEST_VERSION; i++) {
-            String name = "test_" + i;
-            helper.createDatabase(name, i).close();
-            IllegalStateException exception = null;
-            try {
-                MigrationDb db = Room.databaseBuilder(
-                        InstrumentationRegistry.getInstrumentation().getTargetContext(),
-                        MigrationDb.class, name).build();
-                db.runInTransaction(new Runnable() {
-                    @Override
-                    public void run() {
-                        // do nothing
-                    }
-                });
-            } catch (IllegalStateException ex) {
-                exception = ex;
-            }
-            MatcherAssert.assertThat("identity detection should've failed",
-                    exception, notNullValue());
-        }
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationFrom_destructiveMigrationOccursForSuppliedVersion()
-            throws IOException {
-        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 6);
-        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(database);
-        dao.insertIntoEntity1(2, "foo");
-        dao.insertIntoEntity1(3, "bar");
-        database.close();
-        Context targetContext = InstrumentationRegistry.getTargetContext();
-
-        MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
-                .fallbackToDestructiveMigrationFrom(6)
-                .build();
-
-        assertThat(db.dao().loadAllEntity1s().size(), is(0));
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationFrom_suppliedValueIsMigrationStartVersion_exception()
-            throws IOException {
-        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 6);
-        database.close();
-        Context targetContext = InstrumentationRegistry.getTargetContext();
-
-        Throwable throwable = null;
-        try {
-            Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
-                    .addMigrations(MIGRATION_6_7)
-                    .fallbackToDestructiveMigrationFrom(6)
-                    .build();
-        } catch (Throwable t) {
-            throwable = t;
-        }
-
-        assertThat(throwable, is(not(nullValue())));
-        //noinspection ConstantConditions
-        assertThat(throwable.getMessage(),
-                startsWith("Inconsistency detected. A Migration was supplied to"));
-        assertThat(throwable.getMessage(), endsWith("6"));
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationFrom_suppliedValueIsMigrationEndVersion_exception()
-            throws IOException {
-        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 5);
-        database.close();
-        Context targetContext = InstrumentationRegistry.getTargetContext();
-
-        Throwable throwable = null;
-        try {
-            Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
-                    .addMigrations(MIGRATION_5_6)
-                    .fallbackToDestructiveMigrationFrom(6)
-                    .build();
-        } catch (Throwable t) {
-            throwable = t;
-        }
-
-        assertThat(throwable, is(not(nullValue())));
-        //noinspection ConstantConditions
-        assertThat(throwable.getMessage(),
-                startsWith("Inconsistency detected. A Migration was supplied to"));
-        assertThat(throwable.getMessage(), endsWith("6"));
-    }
-
-    private void testFailure(int startVersion, int endVersion) throws IOException {
-        final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, startVersion);
-        db.close();
-        Throwable throwable = null;
-        try {
-            helper.runMigrationsAndValidate(TEST_DB, endVersion, true,
-                    new EmptyMigration(startVersion, endVersion));
-        } catch (Throwable t) {
-            throwable = t;
-        }
-        assertThat(throwable, instanceOf(IllegalStateException.class));
-        //noinspection ConstantConditions
-        assertThat(throwable.getMessage(), containsString("Migration failed"));
-    }
-
-    private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
-        @Override
-        public void migrate(SupportSQLiteDatabase database) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity2` ("
-                    + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
-                    + " `name` TEXT)");
-        }
-    };
-
-    private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
-        @Override
-        public void migrate(SupportSQLiteDatabase database) {
-            database.execSQL("ALTER TABLE " + MigrationDb.Entity2.TABLE_NAME
-                    + " ADD COLUMN addedInV3 TEXT");
-        }
-    };
-
-    private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
-        @Override
-        public void migrate(SupportSQLiteDatabase database) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3` (`id` INTEGER NOT NULL,"
-                    + " `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))");
-        }
-    };
-
-    private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
-        @Override
-        public void migrate(SupportSQLiteDatabase database) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3_New` (`id` INTEGER NOT NULL,"
-                    + " `name` TEXT, PRIMARY KEY(`id`))");
-            database.execSQL("INSERT INTO Entity3_New(`id`, `name`) "
-                    + "SELECT `id`, `name` FROM Entity3");
-            database.execSQL("DROP TABLE Entity3");
-            database.execSQL("ALTER TABLE Entity3_New RENAME TO Entity3");
-        }
-    };
-
-    private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
-        @Override
-        public void migrate(SupportSQLiteDatabase database) {
-            database.execSQL("DROP TABLE " + MigrationDb.Entity3.TABLE_NAME);
-        }
-    };
-
-    private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
-        @Override
-        public void migrate(SupportSQLiteDatabase database) {
-            database.execSQL("CREATE TABLE IF NOT EXISTS " + MigrationDb.Entity4.TABLE_NAME
-                    + " (`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`)");
-        }
-    };
-
-    private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_1_2,
-            MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7};
-
-    static final class EmptyMigration extends Migration {
-        EmptyMigration(int startVersion, int endVersion) {
-            super(startVersion, endVersion);
-        }
-
-        @Override
-        public void migrate(SupportSQLiteDatabase database) {
-            // do nothing
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
deleted file mode 100644
index c5af5c0..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.paging;
-
-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 static org.junit.Assert.assertNotNull;
-
-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.DataSource;
-import android.arch.paging.LivePagedListBuilder;
-import android.arch.paging.PagedList;
-import android.arch.persistence.db.SimpleSQLiteQuery;
-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.Pet;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
-import android.support.annotation.Nullable;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class DataSourceFactoryTest extends TestDatabaseTest {
-    @Rule
-    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
-
-    private interface LivePagedListFactory {
-        LiveData<PagedList<User>> create();
-    }
-
-    @Test
-    public void getUsersAsPagedList()
-            throws InterruptedException, ExecutionException, TimeoutException {
-        validateUsersAsPagedList(() -> new LivePagedListBuilder<>(
-                mUserDao.loadPagedByAge(3),
-                new PagedList.Config.Builder()
-                        .setPageSize(10)
-                        .setPrefetchDistance(1)
-                        .setInitialLoadSizeHint(10).build())
-                .build());
-    }
-
-    @Test
-    public void getUsersAsPagedList_ViaRawQuery_WithObservable()
-            throws InterruptedException, ExecutionException, TimeoutException {
-        SimpleSQLiteQuery query = new SimpleSQLiteQuery(
-                "SELECT * FROM user where mAge > ?",
-                new Object[]{3});
-        validateUsersAsPagedList(() -> new LivePagedListBuilder<>(
-                mUserDao.loadPagedByAgeWithObserver(query),
-                new PagedList.Config.Builder()
-                        .setPageSize(10)
-                        .setPrefetchDistance(1)
-                        .setInitialLoadSizeHint(10).build())
-                .build());
-    }
-
-    private void validateUsersAsPagedList(
-            LivePagedListFactory factory)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        mDatabase.beginTransaction();
-        try {
-            for (int i = 0; i < 100; i++) {
-                final User user = TestUtil.createUser(i + 1);
-                user.setAge(i);
-                mUserDao.insert(user);
-            }
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            mDatabase.endTransaction();
-        }
-        assertThat(mUserDao.count(), is(100));
-
-        final LiveData<PagedList<User>> livePagedUsers = factory.create();
-
-        final TestLifecycleOwner testOwner = new TestLifecycleOwner();
-        testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
-        drain();
-        PagedListObserver<User> observer = new PagedListObserver<>();
-
-        observe(livePagedUsers, testOwner, observer);
-        assertThat(observer.get(), nullValue());
-        observer.reset();
-
-        testOwner.handleEvent(Lifecycle.Event.ON_START);
-        drain();
-
-        PagedList<User> pagedList1 = observer.get();
-        assertThat(pagedList1, is(notNullValue()));
-
-        assertThat(pagedList1.size(), is(96));
-        assertThat(getAndLoad(pagedList1, 20), is(nullValue()));
-        drain();
-        assertThat(getAndLoad(pagedList1, 31), nullValue());
-        assertThat(getAndLoad(pagedList1, 20), notNullValue());
-        assertThat(getAndLoad(pagedList1, 16), notNullValue());
-
-        drain();
-        assertThat(getAndLoad(pagedList1, 31), notNullValue());
-        assertThat(getAndLoad(pagedList1, 50), nullValue());
-        drain();
-        assertThat(getAndLoad(pagedList1, 50), notNullValue());
-        observer.reset();
-        // now invalidate the database but don't get the new paged list
-        mUserDao.updateById(50, "foo");
-        assertThat(getAndLoad(pagedList1, 70), nullValue());
-        drain();
-        assertThat(getAndLoad(pagedList1, 70), nullValue());
-        PagedList<User> pagedList = observer.get();
-        assertThat(getAndLoad(pagedList, 70), notNullValue());
-    }
-
-    private <T> T getAndLoad(PagedList<T> list, int pos) {
-        T result = list.get(pos);
-        list.loadAround(pos);
-        return result;
-    }
-
-    private void drain() throws InterruptedException, TimeoutException {
-        mExecutorRule.drainTasks(60, TimeUnit.SECONDS);
-    }
-
-    private void observe(final LiveData liveData, final LifecycleOwner provider,
-            final Observer observer) throws ExecutionException, InterruptedException {
-        FutureTask<Void> futureTask = new FutureTask<>(() -> {
-            //noinspection unchecked
-            liveData.observe(provider, observer);
-            return null;
-        });
-        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
-        futureTask.get();
-    }
-
-    @Test
-    public void withRelation() throws ExecutionException, InterruptedException, TimeoutException {
-        // verify DataSourceFactory can be created from a multi table join
-        DataSource.Factory<Integer, UserAndAllPets> factory =
-                mUserPetDao.dataSourceFactoryMultiTable();
-        LiveData<PagedList<UserAndAllPets>> liveData =
-                new LivePagedListBuilder<>(mUserPetDao.dataSourceFactoryMultiTable(), 10).build();
-        assertNotNull(factory.create());
-
-        PagedListObserver<UserAndAllPets> observer = new PagedListObserver<>();
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        observe(liveData, lifecycleOwner, observer);
-        drain();
-        assertThat(observer.get(), is(Collections.emptyList()));
-
-        observer.reset();
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        drain();
-        final UserAndAllPets noPets = observer.get().get(0);
-        assertThat(noPets.user, is(user));
-
-        observer.reset();
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
-        mPetDao.insertAll(pets);
-
-        drain();
-        final UserAndAllPets withPets = observer.get().get(0);
-        assertThat(withPets.user, is(user));
-        assertThat(withPets.pets, is(Arrays.asList(pets)));
-    }
-
-    static class TestLifecycleOwner implements LifecycleOwner {
-
-        private LifecycleRegistry mLifecycle;
-
-        TestLifecycleOwner() {
-            mLifecycle = new LifecycleRegistry(this);
-        }
-
-        @Override
-        public Lifecycle getLifecycle() {
-            return mLifecycle;
-        }
-
-        void handleEvent(Lifecycle.Event event) {
-            mLifecycle.handleLifecycleEvent(event);
-        }
-    }
-
-    private static class PagedListObserver<T> implements Observer<PagedList<T>> {
-        private PagedList<T> mList;
-
-        void reset() {
-            mList = null;
-        }
-
-        public PagedList<T> get() {
-            return mList;
-        }
-
-        @Override
-        public void onChanged(@Nullable PagedList<T> pagedList) {
-            mList = pagedList;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
deleted file mode 100644
index 89359be..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.paging;
-
-import static junit.framework.Assert.assertFalse;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-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.persistence.room.paging.LimitOffsetDataSource;
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LimitOffsetDataSourceTest extends TestDatabaseTest {
-
-    @After
-    public void teardown() {
-        mUserDao.deleteEverything();
-    }
-
-    private LimitOffsetDataSource<User> loadUsersByAgeDesc() {
-        return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc().create();
-    }
-
-    @Test
-    public void emptyPage() {
-        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
-        assertThat(dataSource.countItems(), is(0));
-    }
-
-    @Test
-    public void loadCount() {
-        createUsers(6);
-        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
-        assertThat(dataSource.countItems(), is(6));
-    }
-
-    @Test
-    public void singleItem() {
-        List<User> users = createUsers(1);
-        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
-        assertThat(dataSource.countItems(), is(1));
-        List<User> initial = dataSource.loadRange(0, 10);
-
-        assertThat(initial.get(0), is(users.get(0)));
-        assertFalse(dataSource.loadRange(1, 10).iterator().hasNext());
-    }
-
-    @Test
-    public void initial() {
-        List<User> users = createUsers(10);
-        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
-        assertThat(dataSource.countItems(), is(10));
-        List<User> initial = dataSource.loadRange(0, 1);
-        assertThat(initial.get(0), is(users.get(0)));
-        List<User> second = dataSource.loadRange(1, 1);
-        assertThat(second.get(0), is(users.get(1)));
-    }
-
-    @Test
-    public void loadAll() {
-        List<User> users = createUsers(10);
-
-        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
-        List<User> all = dataSource.loadRange(0, 10);
-        assertThat(users, is(all));
-    }
-
-    @Test
-    public void loadAfter() {
-        List<User> users = createUsers(10);
-        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
-        List<User> result = dataSource.loadRange(4, 2);
-        assertThat(result, is(users.subList(4, 6)));
-    }
-
-    @NonNull
-    private List<User> createUsers(int count) {
-        List<User> users = new ArrayList<>();
-        for (int i = 0; i < count; i++) {
-            User user = TestUtil.createUser(i);
-            user.setAge(1);
-            mUserDao.insert(user);
-            users.add(user);
-        }
-        return users;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ClearAllTablesTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ClearAllTablesTest.java
deleted file mode 100644
index 70b33c4..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ClearAllTablesTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2018 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.hasItem;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import static org.junit.Assert.assertThat;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.InvalidationTracker;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-
-@SuppressWarnings("WeakerAccess")
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ClearAllTablesTest {
-
-    @Database(version = 1, entities = {Parent.class, Child.class}, exportSchema = false)
-    public abstract static class ClearAllTablesDatabase extends RoomDatabase {
-        abstract ClearAllTablesDao dao();
-    }
-
-    @Entity
-    public static class Parent {
-        @PrimaryKey
-        public long id;
-        public String name;
-
-        public Parent(long id, String name) {
-            this.id = id;
-            this.name = name;
-        }
-    }
-
-    @Entity(foreignKeys = {
-            @ForeignKey(entity = Parent.class, parentColumns = "id", childColumns = "parentId")})
-    public static class Child {
-        @PrimaryKey
-        public long id;
-        public String name;
-        public long parentId;
-
-        public Child(long id, String name, long parentId) {
-            this.id = id;
-            this.name = name;
-            this.parentId = parentId;
-        }
-    }
-
-    @Dao
-    public interface ClearAllTablesDao {
-        @Insert
-        void insertParent(Parent parent);
-
-        @Insert
-        void insertChild(Child child);
-
-        @Query("SELECT COUNT(*) FROM Parent")
-        int countParent();
-
-        @Query("SELECT COUNT(*) FROM Child")
-        int countChild();
-    }
-
-    private ClearAllTablesDatabase mDatabase;
-    private ClearAllTablesDao mDao;
-
-    @Before
-    public void setUp() {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        mDatabase = Room.inMemoryDatabaseBuilder(context, ClearAllTablesDatabase.class).build();
-        mDao = mDatabase.dao();
-    }
-
-    @After
-    public void closeDatabase() {
-        mDatabase.close();
-    }
-
-    @Test
-    public void simple() {
-        mDao.insertParent(new Parent(1, "A"));
-        assertThat(mDao.countParent(), is(1));
-        mDatabase.clearAllTables();
-        assertThat(mDao.countParent(), is(0));
-    }
-
-    @Test
-    public void foreignKey() {
-        mDao.insertParent(new Parent(1, "A"));
-        mDao.insertChild(new Child(1, "a", 1));
-        assertThat(mDao.countParent(), is(1));
-        assertThat(mDao.countChild(), is(1));
-        mDatabase.clearAllTables();
-        assertThat(mDao.countParent(), is(0));
-        assertThat(mDao.countChild(), is(0));
-    }
-
-    @Test
-    public void observer() throws InterruptedException {
-        mDao.insertParent(new Parent(1, "A"));
-        assertThat(mDao.countParent(), is(1));
-        final CountDownLatch latch = new CountDownLatch(1);
-        mDatabase.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("Parent") {
-            @Override
-            public void onInvalidated(@NonNull Set<String> tables) {
-                assertThat(tables, hasSize(1));
-                assertThat(tables, hasItem("Parent"));
-                assertThat(mDao.countParent(), is(0));
-                latch.countDown();
-            }
-        });
-        mDatabase.clearAllTables();
-        assertThat(latch.await(3000, TimeUnit.MILLISECONDS), is(true));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java
deleted file mode 100644
index f81cb70..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CollationTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2018 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.MatcherAssert.assertThat;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.hamcrest.CoreMatchers;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class CollationTest {
-    private CollateDb mDb;
-    private CollateDao mDao;
-    private Locale mDefaultLocale;
-    private final CollateEntity mItem1 = new CollateEntity(1, "abı");
-    private final CollateEntity mItem2 = new CollateEntity(2, "abi");
-    private final CollateEntity mItem3 = new CollateEntity(3, "abj");
-    private final CollateEntity mItem4 = new CollateEntity(4, "abç");
-
-    @Before
-    public void init() {
-        mDefaultLocale = Locale.getDefault();
-    }
-
-    private void initDao(Locale systemLocale) {
-        Locale.setDefault(systemLocale);
-        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
-                CollateDb.class).build();
-        mDao = mDb.dao();
-        mDao.insert(mItem1);
-        mDao.insert(mItem2);
-        mDao.insert(mItem3);
-        mDao.insert(mItem4);
-    }
-
-    @After
-    public void closeDb() {
-        mDb.close();
-        Locale.setDefault(mDefaultLocale);
-    }
-
-    @Test
-    public void localized() {
-        initDao(new Locale("tr", "TR"));
-        List<CollateEntity> result = mDao.sortedByLocalized();
-        assertThat(result, CoreMatchers.is(Arrays.asList(
-                mItem4, mItem1, mItem2, mItem3
-        )));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 21)
-    public void localized_asUnicode() {
-        initDao(Locale.getDefault());
-        List<CollateEntity> result = mDao.sortedByLocalizedAsUnicode();
-        assertThat(result, CoreMatchers.is(Arrays.asList(
-                mItem4, mItem2, mItem1, mItem3
-        )));
-    }
-
-    @Test
-    public void unicode_asLocalized() {
-        initDao(new Locale("tr", "TR"));
-        List<CollateEntity> result = mDao.sortedByUnicodeAsLocalized();
-        assertThat(result, CoreMatchers.is(Arrays.asList(
-                mItem4, mItem1, mItem2, mItem3
-        )));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 21)
-    public void unicode() {
-        initDao(Locale.getDefault());
-        List<CollateEntity> result = mDao.sortedByUnicode();
-        assertThat(result, CoreMatchers.is(Arrays.asList(
-                mItem4, mItem2, mItem1, mItem3
-        )));
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @android.arch.persistence.room.Entity
-    static class CollateEntity {
-        @PrimaryKey
-        public final int id;
-        @ColumnInfo(collate = ColumnInfo.LOCALIZED)
-        public final String localizedName;
-        @ColumnInfo(collate = ColumnInfo.UNICODE)
-        public final String unicodeName;
-
-        CollateEntity(int id, String name) {
-            this.id = id;
-            this.localizedName = name;
-            this.unicodeName = name;
-        }
-
-        CollateEntity(int id, String localizedName, String unicodeName) {
-            this.id = id;
-            this.localizedName = localizedName;
-            this.unicodeName = unicodeName;
-        }
-
-        @SuppressWarnings("SimplifiableIfStatement")
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            CollateEntity that = (CollateEntity) o;
-
-            if (id != that.id) return false;
-            if (!localizedName.equals(that.localizedName)) return false;
-            return unicodeName.equals(that.unicodeName);
-        }
-
-        @Override
-        public int hashCode() {
-            int result = id;
-            result = 31 * result + localizedName.hashCode();
-            result = 31 * result + unicodeName.hashCode();
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return "CollateEntity{"
-                    + "id=" + id
-                    + ", localizedName='" + localizedName + '\''
-                    + ", unicodeName='" + unicodeName + '\''
-                    + '}';
-        }
-    }
-
-    @Dao
-    interface CollateDao {
-        @Query("SELECT * FROM CollateEntity ORDER BY localizedName ASC")
-        List<CollateEntity> sortedByLocalized();
-
-        @Query("SELECT * FROM CollateEntity ORDER BY localizedName COLLATE UNICODE ASC")
-        List<CollateEntity> sortedByLocalizedAsUnicode();
-
-        @Query("SELECT * FROM CollateEntity ORDER BY unicodeName ASC")
-        List<CollateEntity> sortedByUnicode();
-
-        @Query("SELECT * FROM CollateEntity ORDER BY unicodeName COLLATE LOCALIZED ASC")
-        List<CollateEntity> sortedByUnicodeAsLocalized();
-
-        @Insert
-        void insert(CollateEntity... entities);
-    }
-
-    @Database(entities = CollateEntity.class, version = 1, exportSchema = false)
-    abstract static class CollateDb extends RoomDatabase {
-        abstract CollateDao dao();
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ConstructorTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ConstructorTest.java
deleted file mode 100644
index 46c875c..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ConstructorTest.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Embedded;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-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;
-
-@RunWith(AndroidJUnit4.class)
-@SuppressWarnings("SqlNoDataSourceInspection")
-@SmallTest
-public class ConstructorTest {
-    @Database(version = 1, entities = {FullConstructor.class, PartialConstructor.class,
-            EntityWithAnnotations.class},
-            exportSchema = false)
-    abstract static class MyDb extends RoomDatabase {
-        abstract MyDao dao();
-    }
-
-    @Dao
-    interface MyDao {
-        @Insert
-        void insertFull(FullConstructor... full);
-
-        @Query("SELECT * FROM fc WHERE a = :a")
-        FullConstructor loadFull(int a);
-
-        @Insert
-        void insertPartial(PartialConstructor... partial);
-
-        @Query("SELECT * FROM pc WHERE a = :a")
-        PartialConstructor loadPartial(int a);
-
-        @Insert
-        void insertEntityWithAnnotations(EntityWithAnnotations... items);
-
-        @Query("SELECT * FROM EntityWithAnnotations")
-        EntityWithAnnotations getEntitiWithAnnotations();
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity(tableName = "fc")
-    static class FullConstructor {
-        @PrimaryKey
-        public final int a;
-        public final int b;
-        @Embedded
-        public final MyEmbedded embedded;
-
-        FullConstructor(int a, int b, MyEmbedded embedded) {
-            this.a = a;
-            this.b = b;
-            this.embedded = embedded;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            FullConstructor that = (FullConstructor) o;
-
-            if (a != that.a) return false;
-            //noinspection SimplifiableIfStatement
-            if (b != that.b) return false;
-            return embedded != null ? embedded.equals(that.embedded)
-                    : that.embedded == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = a;
-            result = 31 * result + b;
-            result = 31 * result + (embedded != null ? embedded.hashCode() : 0);
-            return result;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity(tableName = "pc")
-    static class PartialConstructor {
-        @PrimaryKey
-        public final int a;
-        public int b;
-        @Embedded
-        private MyEmbedded mEmbedded;
-
-        PartialConstructor(int a) {
-            this.a = a;
-        }
-
-        public MyEmbedded getEmbedded() {
-            return mEmbedded;
-        }
-
-        public void setEmbedded(MyEmbedded embedded) {
-            mEmbedded = embedded;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            PartialConstructor that = (PartialConstructor) o;
-
-            if (a != that.a) return false;
-            //noinspection SimplifiableIfStatement
-            if (b != that.b) return false;
-            return mEmbedded != null ? mEmbedded.equals(that.mEmbedded)
-                    : that.mEmbedded == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = a;
-            result = 31 * result + b;
-            result = 31 * result + (mEmbedded != null ? mEmbedded.hashCode() : 0);
-            return result;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class MyEmbedded {
-        public final String text;
-
-        MyEmbedded(String text) {
-            this.text = text;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            MyEmbedded that = (MyEmbedded) o;
-
-            return text != null ? text.equals(that.text) : that.text == null;
-        }
-
-        @Override
-        public int hashCode() {
-            return text != null ? text.hashCode() : 0;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity
-    static class EntityWithAnnotations {
-        @PrimaryKey
-        @NonNull
-        public final String id;
-
-        @NonNull
-        public final String username;
-
-        @Nullable
-        public final String displayName;
-
-        EntityWithAnnotations(
-                @NonNull String id,
-                @NonNull String username,
-                @Nullable String displayName) {
-            this.id = id;
-            this.username = username;
-            this.displayName = displayName;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            EntityWithAnnotations that = (EntityWithAnnotations) o;
-
-            if (!id.equals(that.id)) return false;
-            if (!username.equals(that.username)) return false;
-            return displayName != null ? displayName.equals(that.displayName)
-                    : that.displayName == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = id.hashCode();
-            result = 31 * result + username.hashCode();
-            result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
-            return result;
-        }
-    }
-
-    private MyDb mDb;
-    private MyDao mDao;
-
-    @Before
-    public void init() {
-        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(), MyDb.class)
-                .build();
-        mDao = mDb.dao();
-    }
-
-    @Test
-    public void insertAndReadFullConstructor() {
-        FullConstructor inserted = new FullConstructor(1, 2, null);
-        mDao.insertFull(inserted);
-        final FullConstructor load = mDao.loadFull(1);
-        assertThat(load, is(inserted));
-    }
-
-    @Test
-    public void insertAndReadPartial() {
-        PartialConstructor item = new PartialConstructor(3);
-        item.b = 7;
-        mDao.insertPartial(item);
-        PartialConstructor load = mDao.loadPartial(3);
-        assertThat(load, is(item));
-    }
-
-    @Test // for bug b/69562125
-    public void entityWithAnnotations() {
-        EntityWithAnnotations item = new EntityWithAnnotations("a", "b", null);
-        mDao.insertEntityWithAnnotations(item);
-        assertThat(mDao.getEntitiWithAnnotations(), is(item));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
deleted file mode 100644
index 6f44546..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.mockito.AdditionalAnswers.delegatesTo;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.integration.testapp.database.Customer;
-import android.arch.persistence.room.integration.testapp.database.SampleDatabase;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class CustomDatabaseTest {
-
-    @Test
-    public void invalidationTrackerAfterClose() {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        RoomDatabase.Builder<SampleDatabase> builder =
-                Room.databaseBuilder(context, SampleDatabase.class, "db")
-                        .openHelperFactory(new RethrowExceptionFactory());
-        Customer customer = new Customer();
-        for (int i = 0; i < 100; i++) {
-            SampleDatabase db = builder.build();
-            db.getCustomerDao().insert(customer);
-            // Give InvalidationTracker enough time to start #mRefreshRunnable and pass the
-            // initialization check.
-            SystemClock.sleep(1);
-            // InvalidationTracker#mRefreshRunnable will cause race condition if its database query
-            // happens after close.
-            db.close();
-        }
-    }
-
-    /**
-     * This is mostly {@link FrameworkSQLiteOpenHelperFactory}, but the returned {@link
-     * SupportSQLiteDatabase} fails with {@link RuntimeException} instead of {@link
-     * IllegalStateException} or {@link SQLiteException}. This way, we can simulate custom database
-     * implementation that throws its own exception types.
-     */
-    private static class RethrowExceptionFactory implements SupportSQLiteOpenHelper.Factory {
-
-        @Override
-        public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
-            final FrameworkSQLiteOpenHelperFactory factory = new FrameworkSQLiteOpenHelperFactory();
-            final SupportSQLiteOpenHelper helper = factory.create(configuration);
-            SupportSQLiteOpenHelper helperMock = mock(SupportSQLiteOpenHelper.class,
-                    delegatesTo(helper));
-            // Inject mocks to the object hierarchy.
-            doAnswer(new Answer() {
-                @Override
-                public SupportSQLiteDatabase answer(InvocationOnMock invocation)
-                        throws Throwable {
-                    final SupportSQLiteDatabase db = helper.getWritableDatabase();
-                    SupportSQLiteDatabase dbMock = mock(SupportSQLiteDatabase.class,
-                            delegatesTo(db));
-                    doAnswer(new Answer() {
-                        @Override
-                        public Cursor answer(InvocationOnMock invocation) throws Throwable {
-                            SupportSQLiteQuery query = invocation.getArgument(0);
-                            try {
-                                return db.query(query);
-                            } catch (IllegalStateException | SQLiteException e) {
-                                // Rethrow the exception in order to simulate the way custom
-                                // database implementation throws its own exception types.
-                                throw new RuntimeException("closed", e);
-                            }
-                        }
-                    }).when(dbMock).query(any(SupportSQLiteQuery.class));
-                    return dbMock;
-                }
-            }).when(helperMock).getWritableDatabase();
-            return helperMock;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DaoNameConflictTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DaoNameConflictTest.java
deleted file mode 100644
index 07c0369..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DaoNameConflictTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright 2018 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.MatcherAssert.assertThat;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class DaoNameConflictTest {
-    private ConflictDatabase mDb;
-    @Before
-    public void init() {
-        mDb = Room.inMemoryDatabaseBuilder(
-                InstrumentationRegistry.getTargetContext(),
-                ConflictDatabase.class
-        ).build();
-    }
-
-    @After
-    public void close() {
-        mDb.close();
-    }
-
-    @Test
-    public void readFromItem1() {
-        Item1 item1 = new Item1(1, "a");
-        mDb.item1Dao().insert(item1);
-        Item2 item2 = new Item2(2, "b");
-        mDb.item2Dao().insert(item2);
-        assertThat(mDb.item1Dao().get(), is(item1));
-        assertThat(mDb.item2Dao().get(), is(item2));
-    }
-
-    @Entity
-    static class Item1 {
-        @PrimaryKey
-        public int id;
-        public String name;
-
-        Item1(int id, String name) {
-            this.id = id;
-            this.name = name;
-        }
-
-        @Dao
-        public interface Store {
-            @Query("SELECT * FROM Item1 LIMIT 1")
-            Item1 get();
-            @Insert
-            void insert(Item1... items);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Item1 item1 = (Item1) o;
-
-            //noinspection SimplifiableIfStatement
-            if (id != item1.id) return false;
-            return name != null ? name.equals(item1.name) : item1.name == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = id;
-            result = 31 * result + (name != null ? name.hashCode() : 0);
-            return result;
-        }
-    }
-
-    @Entity
-    static class Item2 {
-        @PrimaryKey
-        public int id;
-        public String name;
-
-        Item2(int id, String name) {
-            this.id = id;
-            this.name = name;
-        }
-
-        @Dao
-        public interface Store {
-            @Query("SELECT * FROM Item2 LIMIT 1")
-            Item2 get();
-            @Insert
-            void insert(Item2... items);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Item2 item2 = (Item2) o;
-
-            //noinspection SimplifiableIfStatement
-            if (id != item2.id) return false;
-            return name != null ? name.equals(item2.name) : item2.name == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = id;
-            result = 31 * result + (name != null ? name.hashCode() : 0);
-            return result;
-        }
-    }
-
-    @Database(version = 1, exportSchema = false, entities = {Item1.class, Item2.class})
-    public abstract static class ConflictDatabase extends RoomDatabase {
-        public abstract Item1.Store item1Dao();
-        public abstract Item2.Store item2Dao();
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DatabaseCallbackTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DatabaseCallbackTest.java
deleted file mode 100644
index fafcc2c..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/DatabaseCallbackTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.core.IsCollectionContaining.hasItem;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.content.Context;
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class DatabaseCallbackTest {
-
-    @Test
-    @MediumTest
-    public void createAndOpen() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        TestDatabaseCallback callback1 = new TestDatabaseCallback();
-        TestDatabase db1 = null;
-        TestDatabase db2 = null;
-        try {
-            db1 = Room.databaseBuilder(context, TestDatabase.class, "test")
-                    .addCallback(callback1)
-                    .build();
-            assertFalse(callback1.mCreated);
-            assertFalse(callback1.mOpened);
-            User user1 = TestUtil.createUser(3);
-            user1.setName("george");
-            db1.getUserDao().insert(user1);
-            assertTrue(callback1.mCreated);
-            assertTrue(callback1.mOpened);
-            TestDatabaseCallback callback2 = new TestDatabaseCallback();
-            db2 = Room.databaseBuilder(context, TestDatabase.class, "test")
-                    .addCallback(callback2)
-                    .build();
-            assertFalse(callback2.mCreated);
-            assertFalse(callback2.mOpened);
-            User user2 = db2.getUserDao().load(3);
-            assertThat(user2.getName(), is("george"));
-            assertFalse(callback2.mCreated); // Not called; already created by db1
-            assertTrue(callback2.mOpened);
-        } finally {
-            if (db1 != null) {
-                db1.close();
-            }
-            if (db2 != null) {
-                db2.close();
-            }
-            assertTrue(context.deleteDatabase("test"));
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void writeOnCreate() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .addCallback(new RoomDatabase.Callback() {
-                    @Override
-                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
-                        Cursor cursor = null;
-                        try {
-                            cursor = db.query(
-                                    "SELECT name FROM sqlite_master WHERE type = 'table'");
-                            ArrayList<String> names = new ArrayList<>();
-                            while (cursor.moveToNext()) {
-                                names.add(cursor.getString(0));
-                            }
-                            assertThat(names, hasItem("User"));
-                        } finally {
-                            if (cursor != null) {
-                                cursor.close();
-                            }
-                        }
-                    }
-                })
-                .build();
-        List<Integer> ids = db.getUserDao().loadIds();
-        assertThat(ids, is(empty()));
-    }
-
-    public static class TestDatabaseCallback extends RoomDatabase.Callback {
-
-        boolean mCreated;
-        boolean mOpened;
-
-        @Override
-        public void onCreate(@NonNull SupportSQLiteDatabase db) {
-            mCreated = true;
-        }
-
-        @Override
-        public void onOpen(@NonNull SupportSQLiteDatabase db) {
-            mOpened = true;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/EmbeddedTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/EmbeddedTest.java
deleted file mode 100644
index d680f3d..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/EmbeddedTest.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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.persistence.room.Room;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-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;
-import android.arch.persistence.room.integration.testapp.dao.UserDao;
-import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
-import android.arch.persistence.room.integration.testapp.vo.Coordinates;
-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.School;
-import android.arch.persistence.room.integration.testapp.vo.SchoolRef;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.arch.persistence.room.integration.testapp.vo.UserAndPet;
-import android.arch.persistence.room.integration.testapp.vo.UserAndPetNonNull;
-import android.content.Context;
-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.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class EmbeddedTest {
-    private UserDao mUserDao;
-    private PetDao mPetDao;
-    private UserPetDao mUserPetDao;
-    private SchoolDao mSchoolDao;
-    private PetCoupleDao mPetCoupleDao;
-
-    @Before
-    public void createDb() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        mUserDao = db.getUserDao();
-        mPetDao = db.getPetDao();
-        mUserPetDao = db.getUserPetDao();
-        mSchoolDao = db.getSchoolDao();
-        mPetCoupleDao = db.getPetCoupleDao();
-    }
-
-    @Test
-    public void loadAll() {
-        Pet pet = TestUtil.createPet(1);
-        User user = TestUtil.createUser(2);
-        pet.setUserId(user.getId());
-        mUserDao.insert(user);
-        mPetDao.insertOrReplace(pet);
-        List<UserAndPet> all = mUserPetDao.loadAll();
-        assertThat(all.size(), is(1));
-        assertThat(all.get(0).getUser(), is(user));
-        assertThat(all.get(0).getPet(), is(pet));
-    }
-
-    @Test
-    public void loadFromUsers() {
-        Pet pet = TestUtil.createPet(1);
-        User user = TestUtil.createUser(2);
-        pet.setUserId(user.getId());
-        mUserDao.insert(user);
-        mPetDao.insertOrReplace(pet);
-        List<UserAndPet> all = mUserPetDao.loadUsers();
-        assertThat(all.size(), is(1));
-        assertThat(all.get(0).getUser(), is(user));
-        assertThat(all.get(0).getPet(), is(pet));
-    }
-
-    @Test
-    public void loadFromUsersWithNullPet() {
-        User user = TestUtil.createUser(2);
-        mUserDao.insert(user);
-        List<UserAndPet> all = mUserPetDao.loadUsers();
-        assertThat(all.size(), is(1));
-        assertThat(all.get(0).getUser(), is(user));
-        assertThat(all.get(0).getPet(), is(nullValue()));
-    }
-
-    @Test
-    public void loadFromUsersWithNonNullPet() {
-        User user = TestUtil.createUser(2);
-        mUserDao.insert(user);
-        List<UserAndPetNonNull> all = mUserPetDao.loadUsersWithNonNullPet();
-        assertThat(all.size(), is(1));
-        assertThat(all.get(0).getUser(), is(user));
-        assertThat(all.get(0).getPet(), is(new Pet()));
-    }
-
-    @Test
-    public void loadFromPets() {
-        Pet pet = TestUtil.createPet(1);
-        User user = TestUtil.createUser(2);
-        pet.setUserId(user.getId());
-        mUserDao.insert(user);
-        mPetDao.insertOrReplace(pet);
-        List<UserAndPet> all = mUserPetDao.loadPets();
-        assertThat(all.size(), is(1));
-        assertThat(all.get(0).getUser(), is(user));
-        assertThat(all.get(0).getPet(), is(pet));
-    }
-
-    @Test
-    public void loadFromPetsWithNullUser() {
-        Pet pet = TestUtil.createPet(1);
-        mPetDao.insertOrReplace(pet);
-        List<UserAndPet> all = mUserPetDao.loadPets();
-        assertThat(all.size(), is(1));
-        assertThat(all.get(0).getUser(), is(nullValue()));
-        assertThat(all.get(0).getPet(), is(pet));
-    }
-
-    @Test
-    public void findSchoolByStreet() {
-        School school = TestUtil.createSchool(3, 5);
-        school.getAddress().setStreet("foo");
-        mSchoolDao.insert(school);
-        List<School> result = mSchoolDao.findByStreet("foo");
-        assertThat(result.size(), is(1));
-        assertThat(result.get(0), is(school));
-    }
-
-    @Test
-    public void loadSubFieldsAsPojo() throws Exception {
-        loadSubFieldsTest(new Callable<List<School>>() {
-            @Override
-            public List<School> call() throws Exception {
-                List<School> result = new ArrayList<>();
-                for (SchoolRef ref : mSchoolDao.schoolAndManagerNamesAsPojo()) {
-                    result.add(ref);
-                }
-                return result;
-            }
-        });
-    }
-
-    @Test
-    public void loadSubFieldsAsEntity() throws Exception {
-        loadSubFieldsTest(new Callable<List<School>>() {
-            @Override
-            public List<School> call() throws Exception {
-                return mSchoolDao.schoolAndManagerNames();
-            }
-        });
-    }
-
-    public void loadSubFieldsTest(Callable<List<School>> loader) throws Exception {
-        School school = TestUtil.createSchool(3, 5);
-        school.setName("MTV High");
-        school.getManager().setName("chet");
-        mSchoolDao.insert(school);
-
-        School school2 = TestUtil.createSchool(4, 6);
-        school2.setName("MTV Low");
-        school2.setManager(null);
-        mSchoolDao.insert(school2);
-
-        List<School> schools = loader.call();
-        assertThat(schools.size(), is(2));
-        assertThat(schools.get(0).getName(), is("MTV High"));
-        assertThat(schools.get(1).getName(), is("MTV Low"));
-        assertThat(schools.get(0).address, nullValue());
-        assertThat(schools.get(1).address, nullValue());
-        assertThat(schools.get(0).getManager(), notNullValue());
-        assertThat(schools.get(1).getManager(), nullValue());
-        assertThat(schools.get(0).getManager().getName(), is("chet"));
-    }
-
-    @Test
-    public void loadNestedSub() {
-        School school = TestUtil.createSchool(3, 5);
-        school.getAddress().getCoordinates().lat = 3.;
-        school.getAddress().getCoordinates().lng = 4.;
-        mSchoolDao.insert(school);
-        Coordinates coordinates = mSchoolDao.loadCoordinates(3);
-        assertThat(coordinates.lat, is(3.));
-        assertThat(coordinates.lng, is(4.));
-
-        School asSchool = mSchoolDao.loadCoordinatesAsSchool(3);
-        assertThat(asSchool.address.getCoordinates().lat, is(3.));
-        assertThat(asSchool.address.getCoordinates().lng, is(4.));
-        // didn't as for it so don't load
-        assertThat(asSchool.getManager(), nullValue());
-        assertThat(asSchool.address.getStreet(), nullValue());
-    }
-
-    @Test
-    public void sameFieldType() {
-        Pet male = TestUtil.createPet(3);
-        Pet female = TestUtil.createPet(5);
-        PetCouple petCouple = new PetCouple();
-        petCouple.id = "foo";
-        petCouple.male = male;
-        petCouple.setFemale(female);
-        mPetCoupleDao.insert(petCouple);
-        List<PetCouple> petCouples = mPetCoupleDao.loadAll();
-        assertThat(petCouples.size(), is(1));
-        PetCouple loaded = petCouples.get(0);
-        assertThat(loaded.id, is("foo"));
-        assertThat(loaded.male, is(male));
-        assertThat(loaded.getFemale(), is(female));
-    }
-
-    @Test
-    public void sameFieldOneNull() {
-        Pet loneWolf = TestUtil.createPet(3);
-        PetCouple petCouple = new PetCouple();
-        petCouple.id = "foo";
-        petCouple.male = loneWolf;
-        mPetCoupleDao.insert(petCouple);
-        List<PetCouple> petCouples = mPetCoupleDao.loadAll();
-        assertThat(petCouples.size(), is(1));
-        PetCouple loaded = petCouples.get(0);
-        assertThat(loaded.id, is("foo"));
-        assertThat(loaded.male, is(loneWolf));
-        assertThat(loaded.getFemale(), is(nullValue()));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ForeignKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ForeignKeyTest.java
deleted file mode 100644
index 1c94e18..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ForeignKeyTest.java
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.both;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.either;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Delete;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.Ignore;
-import android.arch.persistence.room.Index;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.database.sqlite.SQLiteException;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.hamcrest.Matcher;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Locale;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ForeignKeyTest {
-    @Database(version = 1, entities = {A.class, B.class, C.class, D.class, E.class},
-            exportSchema = false)
-    abstract static class ForeignKeyDb extends RoomDatabase {
-        abstract FkDao dao();
-    }
-
-    @SuppressWarnings({"SqlNoDataSourceInspection", "SameParameterValue"})
-    @Dao
-    interface FkDao {
-        @Insert
-        void insert(A... a);
-
-        @Insert
-        void insert(B... b);
-
-        @Insert
-        void insert(C... c);
-
-        @Insert
-        void insert(D... d);
-
-        @Query("SELECT * FROM A WHERE id = :id")
-        A loadA(int id);
-
-        @Query("SELECT * FROM B WHERE id = :id")
-        B loadB(int id);
-
-        @Query("SELECT * FROM C WHERE id = :id")
-        C loadC(int id);
-
-        @Query("SELECT * FROM D WHERE id = :id")
-        D loadD(int id);
-
-        @Query("SELECT * FROM E WHERE id = :id")
-        E loadE(int id);
-
-        @Delete
-        void delete(A... a);
-
-        @Delete
-        void delete(B... b);
-
-        @Delete
-        void delete(C... c);
-
-        @Query("UPDATE A SET name = :newName WHERE id = :id")
-        void changeNameA(int id, String newName);
-
-        @Insert
-        void insert(E... e);
-
-
-    }
-
-    @Entity(indices = {@Index(value = "name", unique = true),
-            @Index(value = {"name", "lastName"}, unique = true)})
-    static class A {
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public String name;
-        public String lastName;
-
-        A(String name) {
-            this.name = name;
-        }
-
-        @Ignore
-        A(String name, String lastName) {
-            this.name = name;
-            this.lastName = lastName;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity(foreignKeys = {
-            @ForeignKey(entity = A.class,
-                    parentColumns = "name",
-                    childColumns = "aName")})
-
-    static class B {
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public String aName;
-
-        B(String aName) {
-            this.aName = aName;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity(foreignKeys = {
-            @ForeignKey(entity = A.class,
-                    parentColumns = "name",
-                    childColumns = "aName",
-                    deferred = true)})
-    static class C {
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public String aName;
-
-        C(String aName) {
-            this.aName = aName;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity(foreignKeys = {
-            @ForeignKey(entity = A.class,
-                    parentColumns = "name",
-                    childColumns = "aName",
-                    onDelete = ForeignKey.CASCADE,
-                    onUpdate = ForeignKey.CASCADE)})
-    static class D {
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public String aName;
-
-        D(String aName) {
-            this.aName = aName;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity(foreignKeys = {
-            @ForeignKey(entity = A.class,
-                    parentColumns = {"name", "lastName"},
-                    childColumns = {"aName", "aLastName"},
-                    onDelete = ForeignKey.SET_NULL,
-                    onUpdate = ForeignKey.CASCADE)})
-    static class E {
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public String aName;
-        public String aLastName;
-
-        E() {
-        }
-
-        @Ignore
-        E(String aName, String aLastName) {
-            this.aName = aName;
-            this.aLastName = aLastName;
-        }
-    }
-
-
-    private ForeignKeyDb mDb;
-    private FkDao mDao;
-
-    @Before
-    public void openDb() {
-        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
-                ForeignKeyDb.class).build();
-        mDao = mDb.dao();
-    }
-
-    @Test
-    public void simpleForeignKeyFailure() {
-        Throwable t = catchException(new ThrowingRunnable() {
-            @Override
-            public void run() throws Exception {
-                mDao.insert(new B("foo"));
-            }
-        });
-        assertThat(t, instanceOf(SQLiteException.class));
-        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
-    }
-
-    @Test
-    public void simpleForeignKeyDeferredFailure() {
-        Throwable t = catchException(new ThrowingRunnable() {
-            @Override
-            public void run() throws Exception {
-                mDao.insert(new C("foo"));
-            }
-        });
-        assertThat(t, instanceOf(SQLiteException.class));
-        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
-    }
-
-    @Test
-    public void immediateForeignKeyFailure() {
-        Throwable t = catchException(new ThrowingRunnable() {
-            @Override
-            public void run() throws Exception {
-                try {
-                    mDb.beginTransaction();
-                    mDao.insert(new B("foo"));
-                    mDao.insert(new A("foo"));
-                    mDb.setTransactionSuccessful();
-                } finally {
-                    mDb.endTransaction();
-                }
-            }
-        });
-        assertThat(t, instanceOf(SQLiteException.class));
-    }
-
-    @Test
-    public void deferredForeignKeySuccess() {
-        try {
-            mDb.beginTransaction();
-            mDao.insert(new C("foo"));
-            mDao.insert(new A("foo"));
-            mDb.setTransactionSuccessful();
-        } finally {
-            mDb.endTransaction();
-        }
-        assertThat(mDao.loadA(1), notNullValue());
-        assertThat(mDao.loadC(1), notNullValue());
-    }
-
-    @Test
-    public void onDelete_noAction() {
-        mDao.insert(new A("a1"));
-        final A a = mDao.loadA(1);
-        mDao.insert(new B("a1"));
-        Throwable t = catchException(new ThrowingRunnable() {
-            @Override
-            public void run() throws Exception {
-                mDao.delete(a);
-            }
-        });
-        assertThat(t, instanceOf(SQLiteException.class));
-        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
-    }
-
-    @Test
-    public void onDelete_noAction_withTransaction() {
-        mDao.insert(new A("a1"));
-        final A a = mDao.loadA(1);
-        mDao.insert(new B("a1"));
-        final B b = mDao.loadB(1);
-        Throwable t = catchException(new ThrowingRunnable() {
-            @Override
-            public void run() throws Exception {
-                deleteInTransaction(a, b);
-            }
-        });
-        assertThat(t, instanceOf(SQLiteException.class));
-        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
-    }
-
-    @Test
-    public void onDelete_noAction_deferred() {
-        mDao.insert(new A("a1"));
-        final A a = mDao.loadA(1);
-        mDao.insert(new C("a1"));
-        Throwable t = catchException(new ThrowingRunnable() {
-            @Override
-            public void run() throws Exception {
-                mDao.delete(a);
-            }
-        });
-        assertThat(t, instanceOf(SQLiteException.class));
-        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
-    }
-
-    @Test
-    public void onDelete_noAction__deferredWithTransaction() {
-        mDao.insert(new A("a1"));
-        final A a = mDao.loadA(1);
-        mDao.insert(new C("a1"));
-        final C c = mDao.loadC(1);
-        deleteInTransaction(a, c);
-    }
-
-    @Test
-    public void onDelete_cascade() {
-        mDao.insert(new A("a1"));
-        final A a = mDao.loadA(1);
-        mDao.insert(new D("a1"));
-        final D d = mDao.loadD(1);
-        assertThat("test sanity", d, notNullValue());
-        mDao.delete(a);
-        assertThat(mDao.loadD(1), nullValue());
-    }
-
-    @Test
-    public void onUpdate_cascade() {
-        mDao.insert(new A("a1"));
-        mDao.insert(new D("a1"));
-        final D d = mDao.loadD(1);
-        assertThat("test sanity", d, notNullValue());
-        mDao.changeNameA(1, "bla");
-        assertThat(mDao.loadD(1).aName, equalTo("bla"));
-        assertThat(mDao.loadA(1).name, equalTo("bla"));
-    }
-
-    @Test
-    public void multipleReferences() {
-        mDao.insert(new A("a1", "a2"));
-        final A a = mDao.loadA(1);
-        assertThat("test sanity", a, notNullValue());
-        Throwable t = catchException(new ThrowingRunnable() {
-            @Override
-            public void run() throws Exception {
-                mDao.insert(new E("a1", "dsa"));
-            }
-        });
-        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
-    }
-
-    @Test
-    public void onDelete_setNull_multipleReferences() {
-        mDao.insert(new A("a1", "a2"));
-        final A a = mDao.loadA(1);
-        mDao.insert(new E("a1", "a2"));
-        assertThat(mDao.loadE(1), notNullValue());
-        mDao.delete(a);
-        E e = mDao.loadE(1);
-        assertThat(e, notNullValue());
-        assertThat(e.aName, nullValue());
-        assertThat(e.aLastName, nullValue());
-    }
-
-    @Test
-    public void onUpdate_cascade_multipleReferences() {
-        mDao.insert(new A("a1", "a2"));
-        final A a = mDao.loadA(1);
-        mDao.insert(new E("a1", "a2"));
-        assertThat(mDao.loadE(1), notNullValue());
-        mDao.changeNameA(1, "foo");
-        assertThat(mDao.loadE(1), notNullValue());
-        assertThat(mDao.loadE(1).aName, equalTo("foo"));
-        assertThat(mDao.loadE(1).aLastName, equalTo("a2"));
-    }
-
-    private static Matcher<String> foreignKeyErrorMessage() {
-        return either(containsString("FOREIGN KEY"))
-                .or(both(containsString("CODE 19")).and(containsString("CONSTRAINT FAILED")));
-    }
-
-    @SuppressWarnings("Duplicates")
-    private void deleteInTransaction(A a, B b) {
-        mDb.beginTransaction();
-        try {
-            mDao.delete(a);
-            mDao.delete(b);
-            mDb.setTransactionSuccessful();
-        } finally {
-            mDb.endTransaction();
-        }
-    }
-
-    @SuppressWarnings("Duplicates")
-    private void deleteInTransaction(A a, C c) {
-        mDb.beginTransaction();
-        try {
-            mDao.delete(a);
-            mDao.delete(c);
-            mDb.setTransactionSuccessful();
-        } finally {
-            mDb.endTransaction();
-        }
-    }
-
-    private static Throwable catchException(ThrowingRunnable throwingRunnable) {
-        try {
-            throwingRunnable.run();
-        } catch (Throwable t) {
-            return t;
-        }
-        throw new RuntimeException("didn't throw an exception");
-    }
-
-    private interface ThrowingRunnable {
-        void run() throws Exception;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
deleted file mode 100644
index dcefa41..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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.persistence.room.integration.testapp.vo.FunnyNamedEntity;
-import android.support.test.InstrumentationRegistry;
-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];
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> mFunnyNamedDao.observableOne(2).observeForever(
-                        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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/GenericEntityTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/GenericEntityTest.java
deleted file mode 100644
index 8efb7c7..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/GenericEntityTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright 2018 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.MatcherAssert.assertThat;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.support.annotation.NonNull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class GenericEntityTest {
-    private GenericDb mDb;
-    private GenericDao mDao;
-
-    @Before
-    public void init() {
-        mDb = Room.inMemoryDatabaseBuilder(
-                InstrumentationRegistry.getTargetContext(),
-                GenericDb.class
-        ).build();
-        mDao = mDb.getDao();
-    }
-
-    @After
-    public void close() {
-        mDb.close();
-    }
-
-    @Test
-    public void readWriteEntity() {
-        EntityItem item = new EntityItem("abc", "def");
-        mDao.insert(item);
-        EntityItem received = mDao.get("abc");
-        assertThat(received, is(item));
-    }
-
-    @Test
-    public void readPojo() {
-        EntityItem item = new EntityItem("abc", "def");
-        mDao.insert(item);
-        PojoItem received = mDao.getPojo("abc");
-        assertThat(received.id, is("abc"));
-    }
-
-    static class Item<P, F> {
-        @NonNull
-        @PrimaryKey
-        public final P id;
-        private F mField;
-
-        Item(@NonNull P id) {
-            this.id = id;
-        }
-
-        public F getField() {
-            return mField;
-        }
-
-        public void setField(F field) {
-            mField = field;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Item<?, ?> item = (Item<?, ?>) o;
-            //noinspection SimplifiableIfStatement
-            if (!id.equals(item.id)) return false;
-            return mField != null ? mField.equals(item.mField) : item.mField == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = id.hashCode();
-            result = 31 * result + (mField != null ? mField.hashCode() : 0);
-            return result;
-        }
-    }
-
-    static class PojoItem extends Item<String, Integer> {
-        PojoItem(String id) {
-            super(id);
-        }
-    }
-
-    @Entity
-    static class EntityItem extends Item<String, Integer> {
-        public final String name;
-
-        EntityItem(String id, String name) {
-            super(id);
-            this.name = name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            if (!super.equals(o)) return false;
-            EntityItem that = (EntityItem) o;
-            return name != null ? name.equals(that.name) : that.name == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = super.hashCode();
-            result = 31 * result + (name != null ? name.hashCode() : 0);
-            return result;
-        }
-    }
-
-    @Dao
-    public interface GenericDao {
-        @Insert
-        void insert(EntityItem... items);
-
-        @Query("SELECT * FROM EntityItem WHERE id = :id")
-        EntityItem get(String id);
-
-        @Query("SELECT * FROM EntityItem WHERE id = :id")
-        PojoItem getPojo(String id);
-    }
-
-    @Database(version = 1, entities = {EntityItem.class}, exportSchema = false)
-    public abstract static class GenericDb extends RoomDatabase {
-        abstract GenericDao getDao();
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IdentityDetectionTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IdentityDetectionTest.java
deleted file mode 100644
index e00a778..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IdentityDetectionTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.fail;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-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 android.util.Log;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class IdentityDetectionTest {
-    static final String TAG = "IdentityDetectionTest";
-    static final String DB_FILE_NAME = "identity_test_db";
-    TestDatabase mTestDatabase;
-    @Before
-    public void createTestDatabase() {
-        deleteDbFile();
-    }
-
-    @Test
-    public void reOpenWithoutIssues() {
-        openDb();
-        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
-        closeDb();
-        openDb();
-        User[] users = mTestDatabase.getUserDao().loadByIds(3);
-        assertThat(users.length, is(1));
-    }
-
-    @Test
-    public void reOpenChangedHash() {
-        openDb();
-        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
-        // change the hash
-        SupportSQLiteDatabase db = mTestDatabase.getOpenHelper().getWritableDatabase();
-        db.execSQL("UPDATE " + Room.MASTER_TABLE_NAME + " SET `identity_hash` = ?"
-                + " WHERE id = 42", new String[]{"bad hash"});
-        closeDb();
-        Throwable[] exceptions = new Throwable[1];
-        try {
-            openDb();
-            mTestDatabase.getUserDao().loadByIds(3);
-        } catch (Throwable t) {
-            exceptions[0] = t;
-            mTestDatabase = null;
-        }
-        assertThat(exceptions[0], instanceOf(IllegalStateException.class));
-    }
-
-    @Test
-    public void reOpenMasterTableDropped() {
-        openDb();
-        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
-        // drop the master table
-        SupportSQLiteDatabase db = mTestDatabase.getOpenHelper().getWritableDatabase();
-        db.execSQL("DROP TABLE " + Room.MASTER_TABLE_NAME);
-        closeDb();
-        try {
-            openDb();
-            mTestDatabase.getUserDao().loadByIds(3);
-            fail("Was expecting an exception.");
-        } catch (Throwable t) {
-            assertThat(t, instanceOf(IllegalStateException.class));
-        }
-    }
-
-    private void closeDb() {
-        mTestDatabase.close();
-    }
-
-    private void openDb() {
-        mTestDatabase = Room.databaseBuilder(InstrumentationRegistry.getTargetContext(),
-                TestDatabase.class, DB_FILE_NAME).build();
-    }
-
-    @After
-    public void clear() {
-        try {
-            if (mTestDatabase != null) {
-                closeDb();
-            }
-            deleteDbFile();
-        } catch (Throwable t) {
-            Log.e(TAG, "could not close test database", t);
-            throw t;
-        }
-    }
-
-    private void deleteDbFile() {
-        File testDb = InstrumentationRegistry.getTargetContext().getDatabasePath(DB_FILE_NAME);
-        testDb.delete();
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IndexingTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IndexingTest.java
deleted file mode 100644
index 16f916b..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/IndexingTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.Index;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class IndexingTest {
-    @Entity(
-            tableName = "foo_table",
-            indices = {
-                    @Index({"field1", "field2"}),
-                    @Index(value = {"field2", "mId"}, unique = true),
-                    @Index(value = {"field2"}, unique = true, name = "customIndex"),
-            })
-    static class Entity1 {
-        @PrimaryKey
-        public int mId;
-        public String field1;
-        public String field2;
-        @ColumnInfo(index = true, name = "my_field")
-        public String field3;
-    }
-
-    static class IndexInfo {
-        public String name;
-        @ColumnInfo(name = "tbl_name")
-        public String tableName;
-        public String sql;
-    }
-
-    @Dao
-    public interface SqlMasterDao {
-        @Query("SELECT * FROM sqlite_master WHERE type = 'index'")
-        List<IndexInfo> loadIndices();
-    }
-
-    @Database(entities = {Entity1.class}, version = 1, exportSchema = false)
-    abstract static class IndexingDb extends RoomDatabase {
-        abstract SqlMasterDao sqlMasterDao();
-    }
-
-    @Test
-    public void verifyIndices() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        IndexingDb db = Room.inMemoryDatabaseBuilder(context, IndexingDb.class).build();
-        List<IndexInfo> indices = db.sqlMasterDao().loadIndices();
-        assertThat(indices.size(), is(4));
-        for (IndexInfo info : indices) {
-            assertThat(info.tableName, is("foo_table"));
-        }
-        assertThat(indices.get(0).sql, is("CREATE INDEX `index_foo_table_field1_field2`"
-                + " ON `foo_table` (`field1`, `field2`)"));
-        assertThat(indices.get(1).sql, is("CREATE UNIQUE INDEX `index_foo_table_field2_mId`"
-                + " ON `foo_table` (`field2`, `mId`)"));
-        assertThat(indices.get(2).sql, is("CREATE UNIQUE INDEX `customIndex`"
-                + " ON `foo_table` (`field2`)"));
-        assertThat(indices.get(3).sql, is("CREATE INDEX `index_foo_table_my_field`"
-                + " ON `foo_table` (`my_field`)"));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
deleted file mode 100644
index 33f4018..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-
-import android.arch.core.executor.testing.CountingTaskExecutorRule;
-import android.arch.persistence.room.InvalidationTracker;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-import android.arch.persistence.room.integration.testapp.dao.UserDao;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Tests invalidation tracking.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InvalidationTest {
-    @Rule
-    public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule();
-    private UserDao mUserDao;
-    private TestDatabase mDb;
-
-    @Before
-    public void createDb() throws TimeoutException, InterruptedException {
-        Context context = InstrumentationRegistry.getTargetContext();
-        mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        mUserDao = mDb.getUserDao();
-        drain();
-    }
-
-    @After
-    public void closeDb() throws TimeoutException, InterruptedException {
-        mDb.close();
-        drain();
-    }
-
-    private void drain() throws TimeoutException, InterruptedException {
-        executorRule.drainTasks(1, TimeUnit.MINUTES);
-    }
-
-    @Test
-    public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        LoggingObserver observer = new LoggingObserver("User");
-        mDb.getInvalidationTracker().addObserver(observer);
-        drain();
-        mUserDao.updateById(3, "foo2");
-        drain();
-        assertThat(observer.getInvalidatedTables(), hasSize(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("User"));
-    }
-
-    @Test
-    public void testInvalidationOnDelete() throws InterruptedException, TimeoutException {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        LoggingObserver observer = new LoggingObserver("User");
-        mDb.getInvalidationTracker().addObserver(observer);
-        drain();
-        mUserDao.delete(user);
-        drain();
-        assertThat(observer.getInvalidatedTables(), hasSize(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("User"));
-    }
-
-    @Test
-    public void testInvalidationOnInsert() throws InterruptedException, TimeoutException {
-        LoggingObserver observer = new LoggingObserver("User");
-        mDb.getInvalidationTracker().addObserver(observer);
-        drain();
-        mUserDao.insert(TestUtil.createUser(3));
-        drain();
-        assertThat(observer.getInvalidatedTables(), hasSize(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("User"));
-    }
-
-    @Test
-    public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException {
-        LoggingObserver observer = new LoggingObserver("User");
-        mUserDao.insert(TestUtil.createUser(3));
-        drain();
-        mDb.getInvalidationTracker().addObserver(observer);
-        drain();
-        assertThat(observer.getInvalidatedTables(), nullValue());
-    }
-
-    @Test
-    public void testMultipleTables() throws InterruptedException, TimeoutException {
-        LoggingObserver observer = new LoggingObserver("User", "Pet");
-        mDb.getInvalidationTracker().addObserver(observer);
-        drain();
-        mUserDao.insert(TestUtil.createUser(3));
-        drain();
-        assertThat(observer.getInvalidatedTables(), hasSize(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("User"));
-    }
-
-    private static class LoggingObserver extends InvalidationTracker.Observer {
-        private Set<String> mInvalidatedTables;
-
-        LoggingObserver(String... tables) {
-            super(tables);
-        }
-
-        @Override
-        public void onInvalidated(@NonNull Set<String> tables) {
-            mInvalidatedTables = tables;
-        }
-
-        Set<String> getInvalidatedTables() {
-            return mInvalidatedTables;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
deleted file mode 100644
index d073598..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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.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.persistence.room.InvalidationTrackerTrojan;
-import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
-import android.arch.persistence.room.integration.testapp.vo.Pet;
-import android.arch.persistence.room.integration.testapp.vo.PetsToys;
-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;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Tests invalidation tracking.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LiveDataQueryTest extends TestDatabaseTest {
-    @Rule
-    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
-
-    @Test
-    public void observeById() throws InterruptedException, ExecutionException, TimeoutException {
-        final LiveData<User> userLiveData = mUserDao.liveUserById(5);
-        final TestLifecycleOwner testOwner = new TestLifecycleOwner();
-        testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
-        final TestObserver<User> observer = new TestObserver<>();
-        observe(userLiveData, testOwner, observer);
-        assertThat(observer.hasValue(), is(false));
-        observer.reset();
-
-        testOwner.handleEvent(Lifecycle.Event.ON_START);
-        assertThat(observer.get(), is(nullValue()));
-
-        // another id
-        observer.reset();
-        mUserDao.insert(TestUtil.createUser(7));
-        assertThat(observer.get(), is(nullValue()));
-
-        observer.reset();
-        final User u5 = TestUtil.createUser(5);
-        mUserDao.insert(u5);
-        assertThat(observer.get(), is(notNullValue()));
-
-        u5.setName("foo-foo-foo");
-        observer.reset();
-        mUserDao.insertOrReplace(u5);
-        final User updated = observer.get();
-        assertThat(updated, is(notNullValue()));
-        assertThat(updated.getName(), is("foo-foo-foo"));
-
-        testOwner.handleEvent(Lifecycle.Event.ON_STOP);
-        observer.reset();
-        u5.setName("baba");
-        mUserDao.insertOrReplace(u5);
-        assertThat(observer.hasValue(), is(false));
-    }
-
-    @Test
-    public void observeListQuery() throws InterruptedException, ExecutionException,
-            TimeoutException {
-        final LiveData<List<User>> userLiveData = mUserDao.liveUsersListByName("frida");
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        final TestObserver<List<User>> observer = new TestObserver<>();
-        observe(userLiveData, lifecycleOwner, observer);
-        assertThat(observer.get(), is(Collections.<User>emptyList()));
-
-        observer.reset();
-        final User user1 = TestUtil.createUser(3);
-        user1.setName("dog frida");
-        mUserDao.insert(user1);
-        assertThat(observer.get(), is(Collections.singletonList(user1)));
-
-        observer.reset();
-        final User user2 = TestUtil.createUser(5);
-        user2.setName("does not match");
-        mUserDao.insert(user2);
-        assertThat(observer.get(), is(Collections.singletonList(user1)));
-
-        observer.reset();
-        user1.setName("i don't match either");
-        mUserDao.insertOrReplace(user1);
-        assertThat(observer.get(), is(Collections.<User>emptyList()));
-
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP);
-
-        observer.reset();
-        final User user3 = TestUtil.createUser(9);
-        user3.setName("painter frida");
-        mUserDao.insertOrReplace(user3);
-        assertThat(observer.hasValue(), is(false));
-
-        observer.reset();
-        final User user4 = TestUtil.createUser(11);
-        user4.setName("friday");
-        mUserDao.insertOrReplace(user4);
-        assertThat(observer.hasValue(), is(false));
-
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        assertThat(observer.get(), is(Arrays.asList(user4, user3)));
-    }
-
-    @Test
-    public void liveDataWithPojo() throws ExecutionException, InterruptedException,
-            TimeoutException {
-        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
-        users[0].setAge(10);
-        users[0].setWeight(15);
-
-        users[1].setAge(20);
-        users[1].setWeight(25);
-
-        users[2].setAge(20);
-        users[2].setWeight(26);
-
-        users[3].setAge(10);
-        users[3].setWeight(21);
-
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-
-        final TestObserver<AvgWeightByAge> observer = new TestObserver<>();
-        LiveData<AvgWeightByAge> liveData = mUserDao.maxWeightByAgeGroup();
-
-        observe(liveData, lifecycleOwner, observer);
-        assertThat(observer.get(), is(nullValue()));
-
-        observer.reset();
-        mUserDao.insertAll(users);
-        assertThat(observer.get(), is(new AvgWeightByAge(20, 25.5f)));
-
-        observer.reset();
-        User user3 = mUserDao.load(3);
-        user3.setWeight(79);
-        mUserDao.insertOrReplace(user3);
-
-        assertThat(observer.get(), is(new AvgWeightByAge(10, 50)));
-    }
-
-    @Test
-    public void withRelation() throws ExecutionException, InterruptedException, TimeoutException {
-        final LiveData<UserAndAllPets> liveData = mUserPetDao.liveUserWithPets(3);
-        final TestObserver<UserAndAllPets> observer = new TestObserver<>();
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        observe(liveData, lifecycleOwner, observer);
-        assertThat(observer.get(), is(nullValue()));
-
-        observer.reset();
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        final UserAndAllPets noPets = observer.get();
-        assertThat(noPets.user, is(user));
-
-        observer.reset();
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
-        mPetDao.insertAll(pets);
-
-        final UserAndAllPets withPets = observer.get();
-        assertThat(withPets.user, is(user));
-        assertThat(withPets.pets, is(Arrays.asList(pets)));
-    }
-
-    @Test
-    public void withRelationOnly() throws ExecutionException, InterruptedException,
-            TimeoutException {
-        LiveData<PetsToys> liveData = mSpecificDogDao.getSpecificDogsToys();
-
-        PetsToys expected = new PetsToys();
-        expected.petId = 123;
-
-        Toy toy = new Toy();
-        toy.setId(1);
-        toy.setPetId(123);
-        toy.setName("ball");
-
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        final TestObserver<PetsToys> observer = new TestObserver<>();
-        observe(liveData, lifecycleOwner, observer);
-        assertThat(observer.get(), is(expected));
-
-        observer.reset();
-        expected.toys.add(toy);
-        mToyDao.insert(toy);
-        assertThat(observer.get(), is(expected));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    public void withWithClause() throws ExecutionException, InterruptedException,
-            TimeoutException {
-        LiveData<List<String>> actual =
-                mWithClauseDao.getUsersWithFactorialIdsLiveData(0);
-        List<String> expected = new ArrayList<>();
-
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        final TestObserver<List<String>> observer = new TestObserver<>();
-        observe(actual, lifecycleOwner, observer);
-        assertThat(observer.get(), is(expected));
-
-        observer.reset();
-        User user = new User();
-        user.setId(0);
-        user.setName("Zero");
-        mUserDao.insert(user);
-        assertThat(observer.get(), is(expected));
-
-        observer.reset();
-        user = new User();
-        user.setId(1);
-        user.setName("One");
-        mUserDao.insert(user);
-        expected.add("One");
-        assertThat(observer.get(), is(expected));
-
-        observer.reset();
-        user = new User();
-        user.setId(6);
-        user.setName("Six");
-        mUserDao.insert(user);
-        assertThat(observer.get(), is(expected));
-
-        actual = mWithClauseDao.getUsersWithFactorialIdsLiveData(3);
-        observe(actual, lifecycleOwner, observer);
-        expected.add("Six");
-        assertThat(observer.get(), is(expected));
-    }
-
-    @MediumTest
-    @Test
-    public void handleGc() throws ExecutionException, InterruptedException, TimeoutException {
-        LiveData<User> liveData = mUserDao.liveUserById(3);
-        final TestObserver<User> observer = new TestObserver<>();
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        observe(liveData, lifecycleOwner, observer);
-        assertThat(observer.get(), is(nullValue()));
-        observer.reset();
-        final User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        assertThat(observer.get(), is(notNullValue()));
-        observer.reset();
-        forceGc();
-        String name = UUID.randomUUID().toString();
-        mUserDao.updateById(3, name);
-        assertThat(observer.get().getName(), is(name));
-
-        // release references
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                lifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
-            }
-        });
-        WeakReference<LiveData> weakLiveData = new WeakReference<LiveData>(liveData);
-        //noinspection UnusedAssignment
-        liveData = null;
-        forceGc();
-        mUserDao.updateById(3, "Bar");
-        forceGc();
-        assertThat(InvalidationTrackerTrojan.countObservers(mDatabase.getInvalidationTracker()),
-                is(0));
-        assertThat(weakLiveData.get(), nullValue());
-    }
-
-    @Test
-    public void booleanLiveData() throws ExecutionException, InterruptedException,
-            TimeoutException {
-        User user = TestUtil.createUser(3);
-        user.setAdmin(false);
-        LiveData<Boolean> adminLiveData = mUserDao.isAdminLiveData(3);
-        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
-        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
-        final TestObserver<Boolean> observer = new TestObserver<>();
-        observe(adminLiveData, lifecycleOwner, observer);
-        assertThat(observer.get(), is(nullValue()));
-        mUserDao.insert(user);
-        assertThat(observer.get(), is(false));
-        user.setAdmin(true);
-        mUserDao.insertOrReplace(user);
-        assertThat(observer.get(), is(true));
-    }
-
-    private void observe(final LiveData liveData, final LifecycleOwner provider,
-            final Observer observer) throws ExecutionException, InterruptedException {
-        FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                //noinspection unchecked
-                liveData.observe(provider, observer);
-                return null;
-            }
-        });
-        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
-        futureTask.get();
-    }
-
-    private void drain() throws TimeoutException, InterruptedException {
-        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
-    }
-
-    private static void forceGc() {
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-    }
-
-    static class TestLifecycleOwner implements LifecycleOwner {
-
-        private LifecycleRegistry mLifecycle;
-
-        TestLifecycleOwner() {
-            mLifecycle = new LifecycleRegistry(this);
-        }
-
-        @Override
-        public Lifecycle getLifecycle() {
-            return mLifecycle;
-        }
-
-        void handleEvent(Lifecycle.Event event) {
-            mLifecycle.handleLifecycleEvent(event);
-        }
-    }
-
-    private class TestObserver<T> implements Observer<T> {
-        private T mLastData;
-        private boolean mHasValue = false;
-
-        void reset() {
-            mHasValue = false;
-            mLastData = null;
-        }
-
-        @Override
-        public void onChanged(@Nullable T o) {
-            mLastData = o;
-            mHasValue = true;
-        }
-
-        boolean hasValue() throws TimeoutException, InterruptedException {
-            drain();
-            return mHasValue;
-        }
-
-        T get() throws InterruptedException, TimeoutException {
-            drain();
-            assertThat(hasValue(), is(true));
-            return mLastData;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/MainThreadCheckTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/MainThreadCheckTest.java
deleted file mode 100644
index f4ed5dd..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/MainThreadCheckTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.core.util.Function;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MainThreadCheckTest {
-
-    @Test
-    public void testMainThread() {
-        final Throwable error = test(false, new Function<TestDatabase, Void>() {
-            @Override
-            public Void apply(TestDatabase db) {
-                db.getUserDao().load(3);
-                return null;
-            }
-        });
-        assertThat(error, notNullValue());
-        assertThat(error, instanceOf(IllegalStateException.class));
-    }
-
-    @Test
-    public void testFlowableOnMainThread() {
-        final Throwable error = test(false, new Function<TestDatabase, Void>() {
-            @Override
-            public Void apply(TestDatabase db) {
-                db.getUserDao().flowableUserById(3);
-                return null;
-            }
-        });
-        assertThat(error, nullValue());
-    }
-
-    @Test
-    public void testLiveDataOnMainThread() {
-        final Throwable error = test(false, new Function<TestDatabase, Void>() {
-            @Override
-            public Void apply(TestDatabase db) {
-                db.getUserDao().liveUserById(3);
-                return null;
-            }
-        });
-        assertThat(error, nullValue());
-    }
-
-    @Test
-    public void testAllowMainThread() {
-        final Throwable error = test(true, new Function<TestDatabase, Void>() {
-            @Override
-            public Void apply(TestDatabase db) {
-                db.getUserDao().load(3);
-                return null;
-            }
-        });
-        assertThat(error, nullValue());
-    }
-
-    private Throwable test(boolean allowMainThread, final Function<TestDatabase, Void> fun) {
-        Context context = InstrumentationRegistry.getTargetContext();
-        final RoomDatabase.Builder<TestDatabase> builder = Room.inMemoryDatabaseBuilder(
-                context, TestDatabase.class);
-        if (allowMainThread) {
-            builder.allowMainThreadQueries();
-        }
-        final TestDatabase db = builder.build();
-        final AtomicReference<Throwable> error = new AtomicReference<>();
-        try {
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        fun.apply(db);
-                    } catch (Throwable t) {
-                        error.set(t);
-                    }
-                }
-            });
-        } finally {
-            db.close();
-        }
-        return error.get();
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoTest.java
deleted file mode 100644
index b1579fc..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-import android.arch.persistence.room.integration.testapp.dao.UserDao;
-import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class PojoTest {
-    private UserDao mUserDao;
-
-    @Before
-    public void createDb() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        mUserDao = db.getUserDao();
-    }
-
-    @Test
-    public void weightsByAge() {
-        User[] users = TestUtil.createUsersArray(3, 5, 7, 10);
-        users[0].setAge(10);
-        users[0].setWeight(20);
-
-        users[1].setAge(10);
-        users[1].setWeight(30);
-
-        users[2].setAge(15);
-        users[2].setWeight(12);
-
-        users[3].setAge(35);
-        users[3].setWeight(55);
-
-        mUserDao.insertAll(users);
-        assertThat(mUserDao.weightByAge(), is(
-                Arrays.asList(
-                        new AvgWeightByAge(35, 55),
-                        new AvgWeightByAge(10, 25),
-                        new AvgWeightByAge(15, 12)
-                )
-        ));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoWithRelationTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoWithRelationTest.java
deleted file mode 100644
index c3ebfe9..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PojoWithRelationTest.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.persistence.room.integration.testapp.vo.EmbeddedUserAndAllPets;
-import android.arch.persistence.room.integration.testapp.vo.Pet;
-import android.arch.persistence.room.integration.testapp.vo.PetWithToyIds;
-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.arch.persistence.room.integration.testapp.vo.UserAndPetAdoptionDates;
-import android.arch.persistence.room.integration.testapp.vo.UserIdAndPetNames;
-import android.arch.persistence.room.integration.testapp.vo.UserWithPetsAndToys;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PojoWithRelationTest extends TestDatabaseTest {
-    @Test
-    public void fetchAll() {
-        User[] users = TestUtil.createUsersArray(1, 2, 3);
-        Pet[][] userPets = new Pet[3][];
-        mUserDao.insertAll(users);
-        for (User user : users) {
-            Pet[] pets = TestUtil.createPetsForUser(user.getId(), user.getId() * 10,
-                    user.getId() - 1);
-            mPetDao.insertAll(pets);
-            userPets[user.getId() - 1] = pets;
-        }
-        List<UserAndAllPets> usersAndPets = mUserPetDao.loadAllUsersWithTheirPets();
-        assertThat(usersAndPets.size(), is(3));
-        assertThat(usersAndPets.get(0).user, is(users[0]));
-        assertThat(usersAndPets.get(0).pets, is(Collections.<Pet>emptyList()));
-
-        assertThat(usersAndPets.get(1).user, is(users[1]));
-        assertThat(usersAndPets.get(1).pets, is(Arrays.asList(userPets[1])));
-
-        assertThat(usersAndPets.get(2).user, is(users[2]));
-        assertThat(usersAndPets.get(2).pets, is(Arrays.asList(userPets[2])));
-    }
-
-    private void createData() {
-        User[] users = TestUtil.createUsersArray(1, 2);
-        mUserDao.insertAll(users);
-        Pet user1_pet1 = TestUtil.createPet(1);
-        user1_pet1.setUserId(1);
-        user1_pet1.setName("pet1");
-        mPetDao.insertOrReplace(user1_pet1);
-
-        Pet user1_pet2 = TestUtil.createPet(2);
-        user1_pet2.setUserId(1);
-        user1_pet2.setName("pet2");
-        mPetDao.insertOrReplace(user1_pet2);
-
-        Pet user2_pet1 = TestUtil.createPet(3);
-        user2_pet1.setUserId(2);
-        user2_pet1.setName("pet3");
-        mPetDao.insertOrReplace(user2_pet1);
-    }
-
-    @Test
-    public void fetchWithNames() {
-        createData();
-
-        List<UserIdAndPetNames> usersAndPets = mUserPetDao.loadUserAndPetNames();
-        assertThat(usersAndPets.size(), is(2));
-        assertThat(usersAndPets.get(0).userId, is(1));
-        assertThat(usersAndPets.get(1).userId, is(2));
-        assertThat(usersAndPets.get(0).names, is(Arrays.asList("pet1", "pet2")));
-        assertThat(usersAndPets.get(1).names, is(Collections.singletonList("pet3")));
-    }
-
-    @Test
-    public void nested() {
-        createData();
-        Toy pet1_toy1 = new Toy();
-        pet1_toy1.setName("toy1");
-        pet1_toy1.setPetId(1);
-        Toy pet1_toy2 = new Toy();
-        pet1_toy2.setName("toy2");
-        pet1_toy2.setPetId(1);
-        mToyDao.insert(pet1_toy1, pet1_toy2);
-        List<UserWithPetsAndToys> userWithPetsAndToys = mUserPetDao.loadUserWithPetsAndToys();
-        assertThat(userWithPetsAndToys.size(), is(2));
-        UserWithPetsAndToys first = userWithPetsAndToys.get(0);
-        List<Toy> toys = first.pets.get(0).toys;
-        assertThat(toys.get(0).getName(), is("toy1"));
-        assertThat(toys.get(1).getName(), is("toy2"));
-        assertThat(userWithPetsAndToys.get(1).pets.get(0).toys, is(Collections.<Toy>emptyList()));
-    }
-
-    @Test
-    public void duplicateParentField() {
-        User[] users = TestUtil.createUsersArray(1, 2);
-        Pet[] pets_1 = TestUtil.createPetsForUser(1, 1, 2);
-        Pet[] pets_2 = TestUtil.createPetsForUser(2, 10, 1);
-        mUserDao.insertAll(users);
-        mPetDao.insertAll(pets_1);
-        mPetDao.insertAll(pets_2);
-        List<UserAndAllPets> userAndAllPets = mUserPetDao.unionByItself();
-        assertThat(userAndAllPets.size(), is(4));
-        for (int i = 0; i < 4; i++) {
-            assertThat("user at " + i, userAndAllPets.get(i).user, is(users[i % 2]));
-        }
-        assertThat(userAndAllPets.get(0).pets, is(Arrays.asList(pets_1)));
-        assertThat(userAndAllPets.get(2).pets, is(Arrays.asList(pets_1)));
-
-        assertThat(userAndAllPets.get(1).pets, is(Arrays.asList(pets_2)));
-        assertThat(userAndAllPets.get(3).pets, is(Arrays.asList(pets_2)));
-    }
-
-    @Test
-    public void embeddedRelation() {
-        createData();
-        EmbeddedUserAndAllPets relationContainer = mUserPetDao.loadUserAndPetsAsEmbedded(1);
-        assertThat(relationContainer.getUserAndAllPets(), notNullValue());
-        assertThat(relationContainer.getUserAndAllPets().user.getId(), is(1));
-        assertThat(relationContainer.getUserAndAllPets().pets.size(), is(2));
-    }
-
-    @Test
-    public void boxedPrimitiveList() {
-        Pet pet1 = TestUtil.createPet(3);
-        Pet pet2 = TestUtil.createPet(5);
-
-        Toy pet1_toy1 = TestUtil.createToyForPet(pet1, 10);
-        Toy pet1_toy2 = TestUtil.createToyForPet(pet1, 20);
-        Toy pet2_toy1 = TestUtil.createToyForPet(pet2, 30);
-
-        mPetDao.insertOrReplace(pet1, pet2);
-        mToyDao.insert(pet1_toy1, pet1_toy2, pet2_toy1);
-
-        List<PetWithToyIds> petWithToyIds = mPetDao.allPetsWithToyIds();
-        //noinspection ArraysAsListWithZeroOrOneArgument
-        assertThat(petWithToyIds, is(
-                Arrays.asList(
-                        new PetWithToyIds(pet1, Arrays.asList(10, 20)),
-                        new PetWithToyIds(pet2, Arrays.asList(30)))
-        ));
-    }
-
-    @Test
-    public void viaTypeConverter() {
-        User user = TestUtil.createUser(3);
-        Pet pet1 = TestUtil.createPet(3);
-        Date date1 = new Date(300);
-        pet1.setAdoptionDate(date1);
-        Pet pet2 = TestUtil.createPet(5);
-        Date date2 = new Date(700);
-        pet2.setAdoptionDate(date2);
-
-        pet1.setUserId(3);
-        pet2.setUserId(3);
-        mUserDao.insert(user);
-        mPetDao.insertOrReplace(pet1, pet2);
-
-        List<UserAndPetAdoptionDates> adoptions =
-                mUserPetDao.loadUserWithPetAdoptionDates();
-
-        assertThat(adoptions, is(Arrays.asList(
-                new UserAndPetAdoptionDates(user, Arrays.asList(new Date(300), new Date(700)))
-        )));
-    }
-
-    @Test
-    public void largeRelation_child() {
-        User user = TestUtil.createUser(3);
-        List<Pet> pets = new ArrayList<>();
-        for (int i = 0; i < 2000; i++) {
-            Pet pet = TestUtil.createPet(i + 1);
-            pet.setUserId(3);
-        }
-        mUserDao.insert(user);
-        mPetDao.insertAll(pets.toArray(new Pet[pets.size()]));
-        List<UserAndAllPets> result = mUserPetDao.loadAllUsersWithTheirPets();
-        assertThat(result.size(), is(1));
-        assertThat(result.get(0).user, is(user));
-        assertThat(result.get(0).pets, is(pets));
-    }
-
-    @Test
-    public void largeRelation_parent() {
-        final List<User> users = new ArrayList<>();
-        final List<Pet> pets = new ArrayList<>();
-        for (int i = 0; i < 2000; i++) {
-            User user = TestUtil.createUser(i + 1);
-            users.add(user);
-            Pet pet = TestUtil.createPet(i + 1);
-            pet.setUserId(user.getId());
-            pets.add(pet);
-        }
-        mDatabase.runInTransaction(new Runnable() {
-            @Override
-            public void run() {
-                mUserDao.insertAll(users.toArray(new User[users.size()]));
-                mPetDao.insertAll(pets.toArray(new Pet[pets.size()]));
-            }
-        });
-        List<UserAndAllPets> result = mUserPetDao.loadAllUsersWithTheirPets();
-        assertThat(result.size(), is(2000));
-        for (int i = 0; i < 2000; i++) {
-            assertThat(result.get(i).user, is(users.get(i)));
-            assertThat(result.get(i).pets, is(Collections.singletonList(pets.get(i))));
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
deleted file mode 100644
index fda4373..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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 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(),
-                PKeyTestDatabase.class).build();
-    }
-
-    @Test
-    public void integerTest() {
-        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
-        entity.data = "foo";
-        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
-        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(1);
-        assertThat(loaded, notNullValue());
-        assertThat(loaded.data, is(entity.data));
-    }
-
-    @Test
-    public void dontOverrideNullable0() {
-        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
-        entity.pKey = 0;
-        entity.data = "foo";
-        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
-        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(0);
-        assertThat(loaded, notNullValue());
-        assertThat(loaded.data, is(entity.data));
-    }
-
-    @Test
-    public void intTest() {
-        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
-        entity.data = "foo";
-        mDatabase.intPKeyDao().insertMe(entity);
-        IntAutoIncPKeyEntity loaded = mDatabase.intPKeyDao().getMe(1);
-        assertThat(loaded, notNullValue());
-        assertThat(loaded.data, is(entity.data));
-    }
-
-    @Test
-    public void getInsertedId() {
-        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
-        entity.data = "foo";
-        final long id = mDatabase.intPKeyDao().insertAndGetId(entity);
-        assertThat(mDatabase.intPKeyDao().getMe((int) id).data, is("foo"));
-    }
-
-    @Test
-    public void getInsertedIds() {
-        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
-        entity.data = "foo";
-        IntAutoIncPKeyEntity entity2 = new IntAutoIncPKeyEntity();
-        entity2.data = "foo2";
-        final long[] ids = mDatabase.intPKeyDao().insertAndGetIds(entity, entity2);
-        assertThat(mDatabase.intPKeyDao().loadDataById(ids), is(Arrays.asList("foo", "foo2")));
-    }
-
-    @Test
-    public void getInsertedIdFromInteger() {
-        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
-        entity.data = "foo";
-        final long id = mDatabase.integerAutoIncPKeyDao().insertAndGetId(entity);
-        assertThat(mDatabase.integerAutoIncPKeyDao().getMe((int) id).data, is("foo"));
-    }
-
-    @Test
-    public void getInsertedIdsFromInteger() {
-        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
-        entity.data = "foo";
-        IntegerAutoIncPKeyEntity entity2 = new IntegerAutoIncPKeyEntity();
-        entity2.data = "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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
deleted file mode 100644
index 5b9adfe..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.testing.CountingTaskExecutorRule;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LiveData;
-import android.arch.paging.DataSource;
-import android.arch.paging.LivePagedListBuilder;
-import android.arch.paging.PagedList;
-import android.arch.paging.PositionalDataSource;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.Ignore;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Relation;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.RoomWarnings;
-import android.arch.persistence.room.Transaction;
-import android.arch.persistence.room.paging.LimitOffsetDataSource;
-import android.support.annotation.NonNull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import io.reactivex.Flowable;
-import io.reactivex.Maybe;
-import io.reactivex.Single;
-import io.reactivex.observers.TestObserver;
-import io.reactivex.schedulers.Schedulers;
-import io.reactivex.subscribers.TestSubscriber;
-
-@SmallTest
-@RunWith(Parameterized.class)
-@SuppressWarnings("CheckReturnValue")
-public class QueryTransactionTest {
-    @Rule
-    public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule();
-    private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0);
-    private TransactionDb mDb;
-    private final boolean mUseTransactionDao;
-    private Entity1Dao mDao;
-    private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest
-            .TestLifecycleOwner();
-
-    @NonNull
-    @Parameterized.Parameters(name = "useTransaction_{0}")
-    public static Boolean[] getParams() {
-        return new Boolean[]{false, true};
-    }
-
-    public QueryTransactionTest(boolean useTransactionDao) {
-        mUseTransactionDao = useTransactionDao;
-    }
-
-    @Before
-    public void initDb() {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START));
-
-        resetTransactionCount();
-        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
-                TransactionDb.class).build();
-        mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
-        drain();
-    }
-
-    @After
-    public void closeDb() {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY));
-        drain();
-        mDb.close();
-    }
-
-    @Test
-    public void readList() {
-        mDao.insert(new Entity1(1, "foo"));
-        resetTransactionCount();
-
-        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
-        List<Entity1> allEntities = mDao.allEntities();
-        assertTransactionCount(allEntities, expectedTransactionCount);
-    }
-
-    @Test
-    public void liveData() {
-        LiveData<List<Entity1>> listLiveData = mDao.liveData();
-        observeForever(listLiveData);
-        drain();
-        assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList()));
-
-        resetTransactionCount();
-        mDao.insert(new Entity1(1, "foo"));
-        drain();
-
-        //noinspection ConstantConditions
-        assertThat(listLiveData.getValue().size(), is(1));
-        int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
-        assertTransactionCount(listLiveData.getValue(), expectedTransactionCount);
-    }
-
-    @Test
-    public void flowable() {
-        Flowable<List<Entity1>> flowable = mDao.flowable();
-        TestSubscriber<List<Entity1>> subscriber = observe(flowable);
-        drain();
-        assertThat(subscriber.values().size(), is(1));
-
-        resetTransactionCount();
-        mDao.insert(new Entity1(1, "foo"));
-        drain();
-
-        List<Entity1> allEntities = subscriber.values().get(1);
-        assertThat(allEntities.size(), is(1));
-        int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
-        assertTransactionCount(allEntities, expectedTransactionCount);
-    }
-
-    @Test
-    public void maybe() {
-        mDao.insert(new Entity1(1, "foo"));
-        resetTransactionCount();
-
-        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
-        Maybe<List<Entity1>> listMaybe = mDao.maybe();
-        TestObserver<List<Entity1>> observer = observe(listMaybe);
-        drain();
-        List<Entity1> allEntities = observer.values().get(0);
-        assertTransactionCount(allEntities, expectedTransactionCount);
-    }
-
-    @Test
-    public void single() {
-        mDao.insert(new Entity1(1, "foo"));
-        resetTransactionCount();
-
-        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
-        Single<List<Entity1>> listMaybe = mDao.single();
-        TestObserver<List<Entity1>> observer = observe(listMaybe);
-        drain();
-        List<Entity1> allEntities = observer.values().get(0);
-        assertTransactionCount(allEntities, expectedTransactionCount);
-    }
-
-    @Test
-    public void relation() {
-        mDao.insert(new Entity1(1, "foo"));
-        mDao.insert(new Child(1, 1));
-        mDao.insert(new Child(2, 1));
-        resetTransactionCount();
-
-        List<Entity1WithChildren> result = mDao.withRelation();
-        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
-        assertTransactionCountWithChildren(result, expectedTransactionCount);
-    }
-
-    @Test
-    public void pagedList() {
-        LiveData<PagedList<Entity1>> pagedList =
-                new LivePagedListBuilder<>(mDao.pagedList(), 10).build();
-        observeForever(pagedList);
-        drain();
-        assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
-
-        mDao.insert(new Entity1(1, "foo"));
-        drain();
-        //noinspection ConstantConditions
-        assertThat(pagedList.getValue().size(), is(1));
-        assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 2 : 1);
-
-        mDao.insert(new Entity1(2, "bar"));
-        drain();
-        assertThat(pagedList.getValue().size(), is(2));
-        assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 4 : 2);
-    }
-
-    @Test
-    public void dataSource() {
-        mDao.insert(new Entity1(2, "bar"));
-        drain();
-        resetTransactionCount();
-        @SuppressWarnings("deprecation")
-        LimitOffsetDataSource<Entity1> dataSource =
-                (LimitOffsetDataSource<Entity1>) mDao.dataSource();
-        dataSource.loadRange(0, 10);
-        assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
-    }
-
-    private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) {
-        assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
-        assertThat(allEntities.isEmpty(), is(false));
-        for (Entity1 entity1 : allEntities) {
-            assertThat(entity1.transactionId, is(expectedTransactionCount));
-        }
-    }
-
-    private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities,
-            int expectedTransactionCount) {
-        assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
-        assertThat(allEntities.isEmpty(), is(false));
-        for (Entity1WithChildren entity1 : allEntities) {
-            assertThat(entity1.transactionId, is(expectedTransactionCount));
-            assertThat(entity1.children, notNullValue());
-            assertThat(entity1.children.isEmpty(), is(false));
-            for (Child child : entity1.children) {
-                assertThat(child.transactionId, is(expectedTransactionCount));
-            }
-        }
-    }
-
-    private void resetTransactionCount() {
-        sStartedTransactionCount.set(0);
-    }
-
-    private void drain() {
-        try {
-            countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            throw new AssertionError("interrupted", e);
-        } catch (TimeoutException e) {
-            throw new AssertionError("drain timed out", e);
-        }
-    }
-
-    private <T> TestSubscriber<T> observe(final Flowable<T> flowable) {
-        TestSubscriber<T> subscriber = new TestSubscriber<>();
-        flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
-                .subscribeWith(subscriber);
-        return subscriber;
-    }
-
-    private <T> TestObserver<T> observe(final Maybe<T> maybe) {
-        TestObserver<T> observer = new TestObserver<>();
-        maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
-                .subscribeWith(observer);
-        return observer;
-    }
-
-    private <T> TestObserver<T> observe(final Single<T> single) {
-        TestObserver<T> observer = new TestObserver<>();
-        single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
-                .subscribeWith(observer);
-        return observer;
-    }
-
-    private <T> void observeForever(final LiveData<T> liveData) {
-        FutureTask<Void> futureTask = new FutureTask<>(() -> {
-            liveData.observe(mLifecycleOwner, t -> {
-            });
-            return null;
-        });
-        ArchTaskExecutor.getMainThreadExecutor().execute(futureTask);
-        try {
-            futureTask.get();
-        } catch (InterruptedException e) {
-            throw new AssertionError("interrupted", e);
-        } catch (ExecutionException e) {
-            throw new AssertionError("execution error", e);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    static class Entity1WithChildren extends Entity1 {
-        @Relation(entity = Child.class, parentColumn = "id",
-                entityColumn = "entity1Id")
-        public List<Child> children;
-
-        Entity1WithChildren(int id, String value) {
-            super(id, value);
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity
-    static class Child {
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public int entity1Id;
-        @Ignore
-        public final int transactionId = sStartedTransactionCount.get();
-
-        Child(int id, int entity1Id) {
-            this.id = id;
-            this.entity1Id = entity1Id;
-        }
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    @Entity
-    static class Entity1 {
-        @PrimaryKey(autoGenerate = true)
-        public int id;
-        public String value;
-        @Ignore
-        public final int transactionId = sStartedTransactionCount.get();
-
-        Entity1(int id, String value) {
-            this.id = id;
-            this.value = value;
-        }
-    }
-
-    // we don't support dao inheritance for queries so for now, go with this
-    interface Entity1Dao {
-        String SELECT_ALL = "select * from Entity1";
-
-        List<Entity1> allEntities();
-
-        Flowable<List<Entity1>> flowable();
-
-        Maybe<List<Entity1>> maybe();
-
-        Single<List<Entity1>> single();
-
-        LiveData<List<Entity1>> liveData();
-
-        List<Entity1WithChildren> withRelation();
-
-        DataSource.Factory<Integer, Entity1> pagedList();
-
-        PositionalDataSource<Entity1> dataSource();
-
-        @Insert
-        void insert(Entity1 entity1);
-
-        @Insert
-        void insert(Child entity1);
-    }
-
-    @Dao
-    interface EntityDao extends Entity1Dao {
-        @Override
-        @Query(SELECT_ALL)
-        List<Entity1> allEntities();
-
-        @Override
-        @Query(SELECT_ALL)
-        Flowable<List<Entity1>> flowable();
-
-        @Override
-        @Query(SELECT_ALL)
-        LiveData<List<Entity1>> liveData();
-
-        @Override
-        @Query(SELECT_ALL)
-        Maybe<List<Entity1>> maybe();
-
-        @Override
-        @Query(SELECT_ALL)
-        Single<List<Entity1>> single();
-
-        @Override
-        @Query(SELECT_ALL)
-        @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
-        List<Entity1WithChildren> withRelation();
-
-        @Override
-        @Query(SELECT_ALL)
-        DataSource.Factory<Integer, Entity1> pagedList();
-
-        @Override
-        @Query(SELECT_ALL)
-        PositionalDataSource<Entity1> dataSource();
-    }
-
-    @Dao
-    interface TransactionDao extends Entity1Dao {
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        List<Entity1> allEntities();
-
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        Flowable<List<Entity1>> flowable();
-
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        LiveData<List<Entity1>> liveData();
-
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        Maybe<List<Entity1>> maybe();
-
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        Single<List<Entity1>> single();
-
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        List<Entity1WithChildren> withRelation();
-
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        DataSource.Factory<Integer, Entity1> pagedList();
-
-        @Override
-        @Transaction
-        @Query(SELECT_ALL)
-        PositionalDataSource<Entity1> dataSource();
-    }
-
-    @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false)
-    abstract static class TransactionDb extends RoomDatabase {
-        abstract EntityDao dao();
-
-        abstract TransactionDao transactionDao();
-
-        @Override
-        public void beginTransaction() {
-            super.beginTransaction();
-            sStartedTransactionCount.incrementAndGet();
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
deleted file mode 100644
index 67f57ed..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RawQueryTest.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright 2018 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.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import static java.util.Collections.emptyList;
-
-import android.arch.core.executor.testing.CountingTaskExecutorRule;
-import android.arch.lifecycle.LiveData;
-import android.arch.persistence.db.SimpleSQLiteQuery;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.arch.persistence.room.integration.testapp.dao.RawDao;
-import android.arch.persistence.room.integration.testapp.vo.NameAndLastName;
-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.arch.persistence.room.integration.testapp.vo.UserAndPet;
-import android.support.test.InstrumentationRegistry;
-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.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RawQueryTest extends TestDatabaseTest {
-    @Rule
-    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
-
-    @Test
-    public void entity_null() {
-        User user = mRawDao.getUser("SELECT * FROM User WHERE mId = 0");
-        assertThat(user, is(nullValue()));
-    }
-
-    @Test
-    public void entity_one() {
-        User expected = TestUtil.createUser(3);
-        mUserDao.insert(expected);
-        User received = mRawDao.getUser("SELECT * FROM User WHERE mId = 3");
-        assertThat(received, is(expected));
-    }
-
-    @Test
-    public void entity_list() {
-        List<User> expected = TestUtil.createUsersList(1, 2, 3, 4);
-        mUserDao.insertAll(expected.toArray(new User[4]));
-        List<User> received = mRawDao.getUserList("SELECT * FROM User ORDER BY mId ASC");
-        assertThat(received, is(expected));
-    }
-
-    @Test
-    public void entity_liveData_string() throws TimeoutException, InterruptedException {
-        SupportSQLiteQuery query = new SimpleSQLiteQuery(
-                "SELECT * FROM User WHERE mId = ?",
-                new Object[]{3}
-        );
-        liveDataTest(mRawDao.getUserLiveData(query));
-    }
-
-    @Test
-    public void entity_liveData_supportQuery() throws TimeoutException, InterruptedException {
-        liveDataTest(mRawDao.getUserLiveData("SELECT * FROM User WHERE mId = 3"));
-    }
-
-    private void liveDataTest(
-            final LiveData<User> liveData) throws TimeoutException, InterruptedException {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> liveData.observeForever(user -> { }));
-        drain();
-        assertThat(liveData.getValue(), is(nullValue()));
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        drain();
-        assertThat(liveData.getValue(), is(user));
-        user.setLastName("cxZ");
-        mUserDao.insertOrReplace(user);
-        drain();
-        assertThat(liveData.getValue(), is(user));
-    }
-
-    @Test
-    public void entity_supportSql() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM User WHERE mId = ?",
-                new Object[]{3});
-        User received = mRawDao.getUser(query);
-        assertThat(received, is(user));
-    }
-
-    @Test
-    public void embedded() {
-        User user = TestUtil.createUser(3);
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 1);
-        mUserDao.insert(user);
-        mPetDao.insertAll(pets);
-        UserAndPet received = mRawDao.getUserAndPet(
-                "SELECT * FROM User, Pet WHERE User.mId = Pet.mUserId LIMIT 1");
-        assertThat(received.getUser(), is(user));
-        assertThat(received.getPet(), is(pets[0]));
-    }
-
-    @Test
-    public void relation() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 10);
-        mPetDao.insertAll(pets);
-        UserAndAllPets result = mRawDao
-                .getUserAndAllPets("SELECT * FROM User WHERE mId = 3");
-        assertThat(result.user, is(user));
-        assertThat(result.pets, is(Arrays.asList(pets)));
-    }
-
-    @Test
-    public void pojo() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        NameAndLastName result =
-                mRawDao.getUserNameAndLastName("SELECT * FROM User");
-        assertThat(result, is(new NameAndLastName(user.getName(), user.getLastName())));
-    }
-
-    @Test
-    public void pojo_supportSql() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        NameAndLastName result =
-                mRawDao.getUserNameAndLastName(new SimpleSQLiteQuery(
-                        "SELECT * FROM User WHERE mId = ?",
-                        new Object[]{3}
-                ));
-        assertThat(result, is(new NameAndLastName(user.getName(), user.getLastName())));
-    }
-
-    @Test
-    public void pojo_typeConverter() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        RawDao.UserNameAndBirthday result = mRawDao.getUserAndBirthday(
-                "SELECT mName, mBirthday FROM user LIMIT 1");
-        assertThat(result.name, is(user.getName()));
-        assertThat(result.birthday, is(user.getBirthday()));
-    }
-
-    @Test
-    public void embedded_nullField() {
-        User user = TestUtil.createUser(3);
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 1);
-        mUserDao.insert(user);
-        mPetDao.insertAll(pets);
-        UserAndPet received = mRawDao.getUserAndPet("SELECT * FROM User LIMIT 1");
-        assertThat(received.getUser(), is(user));
-        assertThat(received.getPet(), is(nullValue()));
-    }
-
-    @Test
-    public void embedded_list() {
-        User[] users = TestUtil.createUsersArray(3, 5);
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
-        mUserDao.insertAll(users);
-        mPetDao.insertAll(pets);
-        List<UserAndPet> received = mRawDao.getUserAndPetList(
-                "SELECT * FROM User LEFT JOIN Pet ON (User.mId = Pet.mUserId)"
-                        + " ORDER BY mId ASC, mPetId ASC");
-        assertThat(received.size(), is(3));
-        // row 0
-        assertThat(received.get(0).getUser(), is(users[0]));
-        assertThat(received.get(0).getPet(), is(pets[0]));
-        // row 1
-        assertThat(received.get(1).getUser(), is(users[0]));
-        assertThat(received.get(1).getPet(), is(pets[1]));
-        // row 2
-        assertThat(received.get(2).getUser(), is(users[1]));
-        assertThat(received.get(2).getPet(), is(nullValue()));
-    }
-
-    @Test
-    public void count() {
-        mUserDao.insertAll(TestUtil.createUsersArray(3, 5, 7, 10));
-        int count = mRawDao.count("SELECT COUNT(*) FROM User");
-        assertThat(count, is(4));
-    }
-
-    @Test
-    public void embedded_liveData() throws TimeoutException, InterruptedException {
-        LiveData<List<UserAndPet>> liveData = mRawDao.getUserAndPetListObservable(
-                "SELECT * FROM User LEFT JOIN Pet ON (User.mId = Pet.mUserId)"
-                        + " ORDER BY mId ASC, mPetId ASC");
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> liveData.observeForever(user -> {
-                })
-        );
-        drain();
-        assertThat(liveData.getValue(), is(emptyList()));
-
-        User[] users = TestUtil.createUsersArray(3, 5);
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
-        mUserDao.insertAll(users);
-        drain();
-        List<UserAndPet> justUsers = liveData.getValue();
-        //noinspection ConstantConditions
-        assertThat(justUsers.size(), is(2));
-        assertThat(justUsers.get(0).getUser(), is(users[0]));
-        assertThat(justUsers.get(1).getUser(), is(users[1]));
-        assertThat(justUsers.get(0).getPet(), is(nullValue()));
-        assertThat(justUsers.get(1).getPet(), is(nullValue()));
-
-        mPetDao.insertAll(pets);
-        drain();
-        List<UserAndPet> allItems = liveData.getValue();
-        //noinspection ConstantConditions
-        assertThat(allItems.size(), is(3));
-        // row 0
-        assertThat(allItems.get(0).getUser(), is(users[0]));
-        assertThat(allItems.get(0).getPet(), is(pets[0]));
-        // row 1
-        assertThat(allItems.get(1).getUser(), is(users[0]));
-        assertThat(allItems.get(1).getPet(), is(pets[1]));
-        // row 2
-        assertThat(allItems.get(2).getUser(), is(users[1]));
-        assertThat(allItems.get(2).getPet(), is(nullValue()));
-
-        mDatabase.clearAllTables();
-        drain();
-        assertThat(liveData.getValue(), is(emptyList()));
-    }
-
-    @Test
-    public void relation_liveData() throws TimeoutException, InterruptedException {
-        LiveData<UserAndAllPets> liveData = mRawDao
-                .getUserAndAllPetsObservable("SELECT * FROM User WHERE mId = 3");
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                () -> liveData.observeForever(user -> {
-                })
-        );
-        drain();
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        drain();
-        assertThat(liveData.getValue().user, is(user));
-        assertThat(liveData.getValue().pets, is(emptyList()));
-        Pet[] pets = TestUtil.createPetsForUser(3, 1, 5);
-        mPetDao.insertAll(pets);
-        drain();
-        assertThat(liveData.getValue().user, is(user));
-        assertThat(liveData.getValue().pets, is(Arrays.asList(pets)));
-    }
-
-    private void drain() throws TimeoutException, InterruptedException {
-        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RelationWithReservedKeywordTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RelationWithReservedKeywordTest.java
deleted file mode 100644
index b01ed45..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RelationWithReservedKeywordTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2018 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.MatcherAssert.assertThat;
-
-import static java.util.Collections.singletonList;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Embedded;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.Index;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Relation;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.Transaction;
-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.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RelationWithReservedKeywordTest {
-    private MyDatabase mDb;
-
-    @Before
-    public void initDb() {
-        mDb = Room.inMemoryDatabaseBuilder(
-                InstrumentationRegistry.getTargetContext(),
-                MyDatabase.class).build();
-    }
-
-    @Test
-    public void loadRelation() {
-        Category category = new Category(1, "cat1");
-        mDb.getDao().insert(category);
-        Topic topic = new Topic(2, 1, "foo");
-        mDb.getDao().insert(topic);
-        List<CategoryWithTopics> categoryWithTopics = mDb.getDao().loadAll();
-        assertThat(categoryWithTopics.size(), is(1));
-        assertThat(categoryWithTopics.get(0).category, is(category));
-        assertThat(categoryWithTopics.get(0).topics, is(singletonList(topic)));
-    }
-
-    @Entity(tableName = "categories")
-    static class Category {
-
-        @PrimaryKey(autoGenerate = true)
-        public final long id;
-
-        public final String name;
-
-        Category(long id, String name) {
-            this.id = id;
-            this.name = name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Category category = (Category) o;
-            //noinspection SimplifiableIfStatement
-            if (id != category.id) return false;
-            return name != null ? name.equals(category.name) : category.name == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = (int) (id ^ (id >>> 32));
-            result = 31 * result + (name != null ? name.hashCode() : 0);
-            return result;
-        }
-    }
-
-    @Dao
-    interface MyDao {
-        @Transaction
-        @Query("SELECT * FROM categories")
-        List<CategoryWithTopics> loadAll();
-
-        @Insert
-        void insert(Category... categories);
-
-        @Insert
-        void insert(Topic... topics);
-    }
-
-    @Database(
-            entities = {Category.class, Topic.class},
-            version = 1,
-            exportSchema = false)
-    abstract static class MyDatabase extends RoomDatabase {
-        abstract MyDao getDao();
-    }
-
-
-    @SuppressWarnings("WeakerAccess")
-    static class CategoryWithTopics {
-        @Embedded
-        public Category category;
-
-        @Relation(
-                parentColumn = "id",
-                entityColumn = "category_id",
-                entity = Topic.class)
-        public List<Topic> topics;
-    }
-
-    @Entity(
-            tableName = "topics",
-            foreignKeys = @ForeignKey(
-                    entity = Category.class,
-                    parentColumns = "id",
-                    childColumns = "category_id",
-                    onDelete = ForeignKey.CASCADE),
-            indices = @Index("category_id"))
-    static class Topic {
-
-        @PrimaryKey(autoGenerate = true)
-        public final long id;
-
-        @ColumnInfo(name = "category_id")
-        public final long categoryId;
-
-        public final String to;
-
-        Topic(long id, long categoryId, String to) {
-            this.id = id;
-            this.categoryId = categoryId;
-            this.to = to;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Topic topic = (Topic) o;
-            if (id != topic.id) return false;
-            //noinspection SimplifiableIfStatement
-            if (categoryId != topic.categoryId) return false;
-            return to != null ? to.equals(topic.to) : topic.to == null;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = (int) (id ^ (id >>> 32));
-            result = 31 * result + (int) (categoryId ^ (categoryId >>> 32));
-            result = 31 * result + (to != null ? to.hashCode() : 0);
-            return result;
-        }
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
deleted file mode 100644
index 01d071e..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-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;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.Collections;
-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;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RxJava2Test extends TestDatabaseTest {
-
-    private TestScheduler mTestScheduler;
-
-    @Before
-    public void setupSchedulers() {
-        mTestScheduler = new TestScheduler();
-        mTestScheduler.start();
-        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
-            @Override
-            public void executeOnDiskIO(Runnable runnable) {
-                mTestScheduler.scheduleDirect(runnable);
-            }
-
-            @Override
-            public void postToMainThread(Runnable runnable) {
-                Assert.fail("no main thread in this test");
-            }
-
-            @Override
-            public boolean isMainThread() {
-                return false;
-            }
-        });
-    }
-
-    @After
-    public void clearSchedulers() {
-        mTestScheduler.shutdown();
-        ArchTaskExecutor.getInstance().setDelegate(null);
-    }
-
-    private void drain() throws InterruptedException {
-        mTestScheduler.triggerActions();
-    }
-
-    @Test
-    public void maybeUser_Empty() throws InterruptedException {
-        TestObserver<User> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.maybeUserById(3).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        testObserver.assertComplete();
-        testObserver.assertNoValues();
-        disposable.dispose();
-    }
-
-    @Test
-    public void maybeUser_WithData() throws InterruptedException {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        TestObserver<User> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.maybeUserById(3).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        testObserver.assertComplete();
-        testObserver.assertValue(user);
-
-        disposable.dispose();
-    }
-
-    @Test
-    public void maybeUsers_EmptyList() throws InterruptedException {
-        TestObserver<List<User>> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.maybeUsersByIds(3, 5, 7).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        testObserver.assertComplete();
-        testObserver.assertValue(Collections.<User>emptyList());
-        disposable.dispose();
-    }
-
-    @Test
-    public void maybeUsers_WithValue() throws InterruptedException {
-        User[] users = TestUtil.createUsersArray(3, 5);
-        mUserDao.insertAll(users);
-        TestObserver<List<User>> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.maybeUsersByIds(3, 5, 7).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        testObserver.assertComplete();
-        // since this is a clean db, it is ok to rely on the order for the test.
-        testObserver.assertValue(Arrays.asList(users));
-        disposable.dispose();
-    }
-
-    @Test
-    public void singleUser_Empty() throws InterruptedException {
-        TestObserver<User> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.singleUserById(3).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        // figure out which error we should dispatch
-        testObserver.assertError(EmptyResultSetException.class);
-        testObserver.assertNoValues();
-        disposable.dispose();
-    }
-
-    @Test
-    public void singleUser_WithData() throws InterruptedException {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        TestObserver<User> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.singleUserById(3).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        testObserver.assertComplete();
-        testObserver.assertValue(user);
-
-        disposable.dispose();
-    }
-
-    @Test
-    public void singleUsers_EmptyList() throws InterruptedException {
-        TestObserver<List<User>> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.singleUsersByIds(3, 5, 7).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        testObserver.assertComplete();
-        testObserver.assertValue(Collections.<User>emptyList());
-        disposable.dispose();
-    }
-
-    @Test
-    public void singleUsers_WithValue() throws InterruptedException {
-        User[] users = TestUtil.createUsersArray(3, 5);
-        mUserDao.insertAll(users);
-        TestObserver<List<User>> testObserver = new TestObserver<>();
-        Disposable disposable = mUserDao.singleUsersByIds(3, 5, 7).observeOn(mTestScheduler)
-                .subscribeWith(testObserver);
-        drain();
-        testObserver.assertComplete();
-        // since this is a clean db, it is ok to rely on the order for the test.
-        testObserver.assertValue(Arrays.asList(users));
-        disposable.dispose();
-    }
-
-    @Test
-    public void observeOnce() throws InterruptedException {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        drain();
-        TestSubscriber<User> consumer = new TestSubscriber<>();
-        Disposable disposable = mUserDao.flowableUserById(3).subscribeWith(consumer);
-        drain();
-        consumer.assertValue(user);
-        disposable.dispose();
-    }
-
-    @Test
-    public void observeChangeAndDispose() throws InterruptedException {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        drain();
-        TestSubscriber<User> consumer = new TestSubscriber<>();
-        Disposable disposable = mUserDao.flowableUserById(3).observeOn(mTestScheduler)
-                .subscribeWith(consumer);
-        drain();
-        assertThat(consumer.values().get(0), is(user));
-        user.setName("rxy");
-        mUserDao.insertOrReplace(user);
-        drain();
-        User next = consumer.values().get(1);
-        assertThat(next, is(user));
-        disposable.dispose();
-        user.setName("foo");
-        mUserDao.insertOrReplace(user);
-        drain();
-        assertThat(consumer.valueCount(), is(2));
-    }
-
-    @Test
-    @MediumTest
-    public void observeEmpty() throws InterruptedException {
-        TestSubscriber<User> consumer = new TestSubscriber<>();
-        Disposable disposable = mUserDao.flowableUserById(3).observeOn(mTestScheduler)
-                .subscribeWith(consumer);
-        drain();
-        consumer.assertNoValues();
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        drain();
-        assertThat(consumer.values().get(0), is(user));
-        disposable.dispose();
-        user.setAge(88);
-        mUserDao.insertOrReplace(user);
-        drain();
-        assertThat(consumer.valueCount(), is(1));
-    }
-
-    @Test
-    public void flowableCountUsers() throws InterruptedException {
-        TestSubscriber<Integer> consumer = new TestSubscriber<>();
-        mUserDao.flowableCountUsers()
-                .observeOn(mTestScheduler)
-                .subscribe(consumer);
-        drain();
-        assertThat(consumer.values().get(0), is(0));
-        mUserDao.insertAll(TestUtil.createUsersArray(1, 3, 4, 6));
-        drain();
-        assertThat(consumer.values().get(1), is(4));
-        mUserDao.deleteByUids(3, 7);
-        drain();
-        assertThat(consumer.values().get(2), is(3));
-        mUserDao.deleteByUids(101);
-        drain();
-        assertThat(consumer.valueCount(), is(3));
-    }
-
-    @Test
-    @MediumTest
-    public void publisherCountUsers() throws InterruptedException {
-        TestSubscriber<Integer> subscriber = new TestSubscriber<>();
-        mUserDao.publisherCountUsers().subscribe(subscriber);
-        drain();
-        subscriber.assertSubscribed();
-        subscriber.request(2);
-        drain();
-        subscriber.assertValue(0);
-        mUserDao.insert(TestUtil.createUser(2));
-        drain();
-        subscriber.assertValues(0, 1);
-        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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
deleted file mode 100644
index fcd0b00..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
deleted file mode 100644
index 793523c..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ /dev/null
@@ -1,611 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasSize;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-import android.arch.persistence.room.integration.testapp.dao.BlobEntityDao;
-import android.arch.persistence.room.integration.testapp.dao.PetDao;
-import android.arch.persistence.room.integration.testapp.dao.ProductDao;
-import android.arch.persistence.room.integration.testapp.dao.UserDao;
-import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
-import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
-import android.arch.persistence.room.integration.testapp.vo.Day;
-import android.arch.persistence.room.integration.testapp.vo.NameAndLastName;
-import android.arch.persistence.room.integration.testapp.vo.Pet;
-import android.arch.persistence.room.integration.testapp.vo.Product;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteConstraintException;
-import android.database.sqlite.SQLiteException;
-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.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class SimpleEntityReadWriteTest {
-    private UserDao mUserDao;
-    private BlobEntityDao mBlobEntityDao;
-    private PetDao mPetDao;
-    private UserPetDao mUserPetDao;
-    private ProductDao mProductDao;
-
-    @Before
-    public void createDb() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        mUserDao = db.getUserDao();
-        mPetDao = db.getPetDao();
-        mUserPetDao = db.getUserPetDao();
-        mBlobEntityDao = db.getBlobEntityDao();
-        mProductDao = db.getProductDao();
-    }
-
-    @Test
-    public void writeUserAndReadInList() throws Exception {
-        User user = TestUtil.createUser(3);
-        user.setName("george");
-        mUserDao.insert(user);
-        List<User> byName = mUserDao.findUsersByName("george");
-        assertThat(byName.get(0), equalTo(user));
-    }
-
-    @Test
-    public void insertNull() throws Exception {
-        @SuppressWarnings("ConstantConditions")
-        Product product = new Product(1, null);
-        Throwable throwable = null;
-        try {
-            mProductDao.insert(product);
-        } catch (Throwable t) {
-            throwable = t;
-        }
-        assertNotNull("Was expecting an exception", throwable);
-        assertThat(throwable, instanceOf(SQLiteConstraintException.class));
-    }
-
-    @Test
-    public void insertDifferentEntities() throws Exception {
-        User user1 = TestUtil.createUser(3);
-        user1.setName("george");
-        Pet pet = TestUtil.createPet(1);
-        pet.setUserId(3);
-        pet.setName("a");
-        mUserPetDao.insertUserAndPet(user1, pet);
-        assertThat(mUserDao.count(), is(1));
-        List<UserAndAllPets> inserted = mUserPetDao.loadAllUsersWithTheirPets();
-        assertThat(inserted, hasSize(1));
-        assertThat(inserted.get(0).user.getId(), is(3));
-        assertThat(inserted.get(0).user.getName(), is(equalTo("george")));
-        assertThat(inserted.get(0).pets, hasSize(1));
-        assertThat(inserted.get(0).pets.get(0).getPetId(), is(1));
-        assertThat(inserted.get(0).pets.get(0).getName(), is("a"));
-        assertThat(inserted.get(0).pets.get(0).getUserId(), is(3));
-        pet.setName("b");
-        mUserPetDao.updateUsersAndPets(new User[]{user1}, new Pet[]{pet});
-        List<UserAndAllPets> updated = mUserPetDao.loadAllUsersWithTheirPets();
-        assertThat(updated, hasSize(1));
-        assertThat(updated.get(0).pets, hasSize(1));
-        assertThat(updated.get(0).pets.get(0).getName(), is("b"));
-        User user2 = TestUtil.createUser(5);
-        user2.setName("chet");
-        mUserDao.insert(user2);
-        assertThat(mUserDao.count(), is(2));
-        mUserPetDao.delete2UsersAndPets(user1, user2, new Pet[]{pet});
-        List<UserAndAllPets> deleted = mUserPetDao.loadAllUsersWithTheirPets();
-        assertThat(deleted, hasSize(0));
-    }
-
-    @Test
-    public void insertDifferentEntities_transaction() throws Exception {
-        Pet pet = TestUtil.createPet(1);
-        mPetDao.insertOrReplace(pet);
-        assertThat(mPetDao.count(), is(1));
-        User user = TestUtil.createUser(3);
-        try {
-            mUserPetDao.insertUserAndPet(user, pet);
-            fail("Exception expected");
-        } catch (SQLiteConstraintException ignored) {
-        }
-        assertThat(mUserDao.count(), is(0));
-        assertThat(mPetDao.count(), is(1));
-    }
-
-    @Test
-    public void throwExceptionOnConflict() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-
-        User user2 = TestUtil.createUser(3);
-        try {
-            mUserDao.insert(user2);
-            throw new AssertionError("didn't throw in conflicting insertion");
-        } catch (SQLiteException ignored) {
-        }
-    }
-
-    @Test
-    public void replaceOnConflict() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-
-        User user2 = TestUtil.createUser(3);
-        mUserDao.insertOrReplace(user2);
-
-        assertThat(mUserDao.load(3), equalTo(user2));
-        assertThat(mUserDao.load(3), not(equalTo(user)));
-    }
-
-    @Test
-    public void updateSimple() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        user.setName("i am an updated name");
-        assertThat(mUserDao.update(user), is(1));
-        assertThat(mUserDao.load(user.getId()), equalTo(user));
-    }
-
-    @Test
-    public void updateNonExisting() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        User user2 = TestUtil.createUser(4);
-        assertThat(mUserDao.update(user2), is(0));
-    }
-
-    @Test
-    public void updateList() {
-        List<User> users = TestUtil.createUsersList(3, 4, 5);
-        mUserDao.insertAll(users.toArray(new User[3]));
-        for (User user : users) {
-            user.setName("name " + user.getId());
-        }
-        assertThat(mUserDao.updateAll(users), is(3));
-        for (User user : users) {
-            assertThat(mUserDao.load(user.getId()).getName(), is("name " + user.getId()));
-        }
-    }
-
-    @Test
-    public void updateListPartial() {
-        List<User> existingUsers = TestUtil.createUsersList(3, 4, 5);
-        mUserDao.insertAll(existingUsers.toArray(new User[3]));
-        for (User user : existingUsers) {
-            user.setName("name " + user.getId());
-        }
-        List<User> allUsers = TestUtil.createUsersList(7, 8, 9);
-        allUsers.addAll(existingUsers);
-        assertThat(mUserDao.updateAll(allUsers), is(3));
-        for (User user : existingUsers) {
-            assertThat(mUserDao.load(user.getId()).getName(), is("name " + user.getId()));
-        }
-    }
-
-    @Test
-    public void delete() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        assertThat(mUserDao.delete(user), is(1));
-        assertThat(mUserDao.delete(user), is(0));
-        assertThat(mUserDao.load(3), is(nullValue()));
-    }
-
-    @Test
-    public void deleteAll() {
-        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
-        mUserDao.insertAll(users);
-        // there is actually no guarantee for this order by works fine since they are ordered for
-        // the test and it is a new database (no pages to recycle etc)
-        assertThat(mUserDao.loadByIds(3, 5, 7, 9), is(users));
-        int deleteCount = mUserDao.deleteAll(new User[]{users[0], users[3],
-                TestUtil.createUser(9)});
-        assertThat(deleteCount, is(2));
-        assertThat(mUserDao.loadByIds(3, 5, 7, 9), is(new User[]{users[1], users[2]}));
-    }
-
-    @Test
-    public void deleteEverything() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        assertThat(mUserDao.count(), is(1));
-        int count = mUserDao.deleteEverything();
-        assertThat(count, is(1));
-        assertThat(mUserDao.count(), is(0));
-    }
-
-    @Test
-    public void findByBoolean() {
-        User user1 = TestUtil.createUser(3);
-        user1.setAdmin(true);
-        User user2 = TestUtil.createUser(5);
-        user2.setAdmin(false);
-        mUserDao.insert(user1);
-        mUserDao.insert(user2);
-        assertThat(mUserDao.findByAdmin(true), is(Arrays.asList(user1)));
-        assertThat(mUserDao.findByAdmin(false), is(Arrays.asList(user2)));
-    }
-
-    @Test
-    public void returnBoolean() {
-        User user1 = TestUtil.createUser(1);
-        User user2 = TestUtil.createUser(2);
-        user1.setAdmin(true);
-        user2.setAdmin(false);
-        mUserDao.insert(user1);
-        mUserDao.insert(user2);
-        assertThat(mUserDao.isAdmin(1), is(true));
-        assertThat(mUserDao.isAdmin(2), is(false));
-    }
-
-    @Test
-    public void findByCollateNoCase() {
-        User user = TestUtil.createUser(3);
-        user.setCustomField("abc");
-        mUserDao.insert(user);
-        List<User> users = mUserDao.findByCustomField("ABC");
-        assertThat(users, hasSize(1));
-        assertThat(users.get(0).getId(), is(3));
-    }
-
-    @Test
-    public void deleteByAge() {
-        User user1 = TestUtil.createUser(3);
-        user1.setAge(30);
-        User user2 = TestUtil.createUser(5);
-        user2.setAge(45);
-        mUserDao.insert(user1);
-        mUserDao.insert(user2);
-        assertThat(mUserDao.deleteAgeGreaterThan(60), is(0));
-        assertThat(mUserDao.deleteAgeGreaterThan(45), is(0));
-        assertThat(mUserDao.deleteAgeGreaterThan(35), is(1));
-        assertThat(mUserDao.loadByIds(3, 5), is(new User[]{user1}));
-    }
-
-    @Test
-    public void deleteByAgeRange() {
-        User user1 = TestUtil.createUser(3);
-        user1.setAge(30);
-        User user2 = TestUtil.createUser(5);
-        user2.setAge(45);
-        mUserDao.insert(user1);
-        mUserDao.insert(user2);
-        assertThat(mUserDao.deleteByAgeRange(35, 40), is(0));
-        assertThat(mUserDao.deleteByAgeRange(25, 30), is(1));
-        assertThat(mUserDao.loadByIds(3, 5), is(new User[]{user2}));
-    }
-
-    @Test
-    public void deleteByUIds() {
-        User[] users = TestUtil.createUsersArray(3, 5, 7, 9, 11);
-        mUserDao.insertAll(users);
-        assertThat(mUserDao.deleteByUids(2, 4, 6), is(0));
-        assertThat(mUserDao.deleteByUids(3, 11), is(2));
-        assertThat(mUserDao.loadByIds(3, 5, 7, 9, 11), is(new User[]{
-                users[1], users[2], users[3]
-        }));
-    }
-
-    @Test
-    public void updateNameById() {
-        User[] usersArray = TestUtil.createUsersArray(3, 5, 7);
-        mUserDao.insertAll(usersArray);
-        assertThat("test sanity", usersArray[1].getName(), not(equalTo("updated name")));
-        int changed = mUserDao.updateById(5, "updated name");
-        assertThat(changed, is(1));
-        assertThat(mUserDao.load(5).getName(), is("updated name"));
-    }
-
-    @Test
-    public void incrementIds() {
-        User[] usersArr = TestUtil.createUsersArray(2, 4, 6);
-        mUserDao.insertAll(usersArr);
-        mUserDao.incrementIds(1);
-        assertThat(mUserDao.loadIds(), is(Arrays.asList(3, 5, 7)));
-    }
-
-    @Test
-    public void incrementAgeOfAll() {
-        User[] users = TestUtil.createUsersArray(3, 5, 7);
-        users[0].setAge(3);
-        users[1].setAge(5);
-        users[2].setAge(7);
-        mUserDao.insertAll(users);
-        assertThat(mUserDao.count(), is(3));
-        mUserDao.incrementAgeOfAll();
-        for (User user : mUserDao.loadByIds(3, 5, 7)) {
-            assertThat(user.getAge(), is(user.getId() + 1));
-        }
-    }
-
-    @Test
-    public void findByIntQueryParameter() {
-        User user = TestUtil.createUser(1);
-        final String name = "my name";
-        user.setName(name);
-        mUserDao.insert(user);
-        assertThat(mUserDao.findByNameLength(name.length()), is(Collections.singletonList(user)));
-    }
-
-    @Test
-    public void findByIntFieldMatch() {
-        User user = TestUtil.createUser(1);
-        user.setAge(19);
-        mUserDao.insert(user);
-        assertThat(mUserDao.findByAge(19), is(Collections.singletonList(user)));
-    }
-
-    @Test
-    public void customConverterField() {
-        User user = TestUtil.createUser(20);
-        Date theDate = new Date(System.currentTimeMillis() - 200);
-        user.setBirthday(theDate);
-        mUserDao.insert(user);
-        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime() - 100),
-                new Date(theDate.getTime() + 1)).get(0), is(user));
-        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime()),
-                new Date(theDate.getTime() + 1)).size(), is(0));
-    }
-
-    @Test
-    public void renamedField() {
-        User user = TestUtil.createUser(3);
-        user.setCustomField("foo laaa");
-        mUserDao.insertOrReplace(user);
-        User loaded = mUserDao.load(3);
-        assertThat(loaded.getCustomField(), is("foo laaa"));
-        assertThat(loaded, is(user));
-    }
-
-    @Test
-    public void readViaCursor() {
-        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
-        mUserDao.insertAll(users);
-        Cursor cursor = mUserDao.findUsersAsCursor(3, 5, 9);
-        try {
-            assertThat(cursor.getCount(), is(3));
-            assertThat(cursor.moveToNext(), is(true));
-            assertThat(cursor.getInt(0), is(3));
-            assertThat(cursor.moveToNext(), is(true));
-            assertThat(cursor.getInt(0), is(5));
-            assertThat(cursor.moveToNext(), is(true));
-            assertThat(cursor.getInt(0), is(9));
-            assertThat(cursor.moveToNext(), is(false));
-        } finally {
-            cursor.close();
-        }
-    }
-
-    @Test
-    public void readDirectWithTypeAdapter() {
-        User user = TestUtil.createUser(3);
-        user.setBirthday(null);
-        mUserDao.insert(user);
-        assertThat(mUserDao.getBirthday(3), is(nullValue()));
-        Calendar calendar = Calendar.getInstance();
-        calendar.add(Calendar.YEAR, 3);
-        Date birthday = calendar.getTime();
-        user.setBirthday(birthday);
-
-        mUserDao.update(user);
-        assertThat(mUserDao.getBirthday(3), is(birthday));
-    }
-
-    @Test
-    public void emptyInQuery() {
-        User[] users = mUserDao.loadByIds();
-        assertThat(users, is(new User[0]));
-    }
-
-    @Test
-    public void blob() {
-        BlobEntity a = new BlobEntity(1, "abc".getBytes());
-        BlobEntity b = new BlobEntity(2, "def".getBytes());
-        mBlobEntityDao.insert(a);
-        mBlobEntityDao.insert(b);
-        List<BlobEntity> list = mBlobEntityDao.selectAll();
-        assertThat(list, hasSize(2));
-        mBlobEntityDao.updateContent(2, "ghi".getBytes());
-        assertThat(mBlobEntityDao.getContent(2), is(equalTo("ghi".getBytes())));
-    }
-
-    @Test
-    public void transactionByRunnable() {
-        User a = TestUtil.createUser(3);
-        User b = TestUtil.createUser(5);
-        mUserDao.insertBothByRunnable(a, b);
-        assertThat(mUserDao.count(), is(2));
-    }
-
-    @Test
-    public void transactionByRunnable_failure() {
-        User a = TestUtil.createUser(3);
-        User b = TestUtil.createUser(3);
-        boolean caught = false;
-        try {
-            mUserDao.insertBothByRunnable(a, b);
-        } catch (SQLiteConstraintException e) {
-            caught = true;
-        }
-        assertTrue("SQLiteConstraintException expected", caught);
-        assertThat(mUserDao.count(), is(0));
-    }
-
-    @Test
-    public void transactionByCallable() {
-        User a = TestUtil.createUser(3);
-        User b = TestUtil.createUser(5);
-        int count = mUserDao.insertBothByCallable(a, b);
-        assertThat(mUserDao.count(), is(2));
-        assertThat(count, is(2));
-    }
-
-    @Test
-    public void transactionByCallable_failure() {
-        User a = TestUtil.createUser(3);
-        User b = TestUtil.createUser(3);
-        boolean caught = false;
-        try {
-            mUserDao.insertBothByCallable(a, b);
-        } catch (SQLiteConstraintException e) {
-            caught = true;
-        }
-        assertTrue("SQLiteConstraintException expected", caught);
-        assertThat(mUserDao.count(), is(0));
-    }
-
-    @Test
-    public void transactionByDefaultImplementation() {
-        Pet pet1 = TestUtil.createPet(1);
-        mPetDao.insertOrReplace(pet1);
-        assertThat(mPetDao.count(), is(1));
-        assertThat(mPetDao.allIds()[0], is(1));
-        Pet pet2 = TestUtil.createPet(2);
-        mPetDao.deleteAndInsert(pet1, pet2, false);
-        assertThat(mPetDao.count(), is(1));
-        assertThat(mPetDao.allIds()[0], is(2));
-    }
-
-    @Test
-    public void transactionByDefaultImplementation_failure() {
-        Pet pet1 = TestUtil.createPet(1);
-        mPetDao.insertOrReplace(pet1);
-        assertThat(mPetDao.count(), is(1));
-        assertThat(mPetDao.allIds()[0], is(1));
-        Pet pet2 = TestUtil.createPet(2);
-        Throwable throwable = null;
-        try {
-            mPetDao.deleteAndInsert(pet1, pet2, true);
-        } catch (Throwable t) {
-            throwable = t;
-        }
-        assertNotNull("Was expecting an exception", throwable);
-        assertThat(mPetDao.count(), is(1));
-        assertThat(mPetDao.allIds()[0], is(1));
-    }
-
-    @Test
-    public void multipleInParamsFollowedByASingleParam_delete() {
-        User user = TestUtil.createUser(3);
-        user.setAge(30);
-        mUserDao.insert(user);
-        assertThat(mUserDao.deleteByAgeAndIds(20, Arrays.asList(3, 5)), is(0));
-        assertThat(mUserDao.count(), is(1));
-        assertThat(mUserDao.deleteByAgeAndIds(30, Arrays.asList(3, 5)), is(1));
-        assertThat(mUserDao.count(), is(0));
-    }
-
-    @Test
-    public void multipleInParamsFollowedByASingleParam_update() {
-        User user = TestUtil.createUser(3);
-        user.setAge(30);
-        user.setWeight(10f);
-        mUserDao.insert(user);
-        assertThat(mUserDao.updateByAgeAndIds(3f, 20, Arrays.asList(3, 5)), is(0));
-        assertThat(mUserDao.loadByIds(3)[0].getWeight(), is(10f));
-        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));
-    }
-
-    @Test
-    public void tablePrefix_simpleSelect() {
-        User user = TestUtil.createUser(3);
-        mUserDao.insert(user);
-        NameAndLastName result = mUserDao.getNameAndLastName(3);
-        assertThat(result.getName(), is(user.getName()));
-        assertThat(result.getLastName(), is(user.getLastName()));
-    }
-
-    @Test
-    public void enumSet_simpleLoad() {
-        User a = TestUtil.createUser(3);
-        Set<Day> expected = toSet(Day.MONDAY, Day.TUESDAY);
-        a.setWorkDays(expected);
-        mUserDao.insert(a);
-        User loaded = mUserDao.load(3);
-        assertThat(loaded.getWorkDays(), is(expected));
-    }
-
-    @Test
-    public void enumSet_query() {
-        User user1 = TestUtil.createUser(3);
-        user1.setWorkDays(toSet(Day.MONDAY, Day.FRIDAY));
-        User user2 = TestUtil.createUser(5);
-        user2.setWorkDays(toSet(Day.MONDAY, Day.THURSDAY));
-        mUserDao.insert(user1);
-        mUserDao.insert(user2);
-        List<User> empty = mUserDao.findUsersByWorkDays(toSet(Day.WEDNESDAY));
-        assertThat(empty.size(), is(0));
-        List<User> friday = mUserDao.findUsersByWorkDays(toSet(Day.FRIDAY));
-        assertThat(friday, is(Arrays.asList(user1)));
-        List<User> monday = mUserDao.findUsersByWorkDays(toSet(Day.MONDAY));
-        assertThat(monday, is(Arrays.asList(user1, user2)));
-
-    }
-
-    private Set<Day> toSet(Day... days) {
-        return new HashSet<>(Arrays.asList(days));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
deleted file mode 100644
index e2525c4..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-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.RawDao;
-import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
-import android.arch.persistence.room.integration.testapp.dao.SpecificDogDao;
-import android.arch.persistence.room.integration.testapp.dao.ToyDao;
-import android.arch.persistence.room.integration.testapp.dao.UserDao;
-import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
-import android.arch.persistence.room.integration.testapp.dao.WithClauseDao;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-
-import org.junit.Before;
-
-@SuppressWarnings("WeakerAccess")
-public abstract class TestDatabaseTest {
-    protected TestDatabase mDatabase;
-    protected UserDao mUserDao;
-    protected PetDao mPetDao;
-    protected UserPetDao mUserPetDao;
-    protected SchoolDao mSchoolDao;
-    protected PetCoupleDao mPetCoupleDao;
-    protected ToyDao mToyDao;
-    protected SpecificDogDao mSpecificDogDao;
-    protected WithClauseDao mWithClauseDao;
-    protected FunnyNamedDao mFunnyNamedDao;
-    protected RawDao mRawDao;
-
-    @Before
-    public void createDb() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        mDatabase = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        mUserDao = mDatabase.getUserDao();
-        mPetDao = mDatabase.getPetDao();
-        mUserPetDao = mDatabase.getUserPetDao();
-        mSchoolDao = mDatabase.getSchoolDao();
-        mPetCoupleDao = mDatabase.getPetCoupleDao();
-        mToyDao = mDatabase.getToyDao();
-        mSpecificDogDao = mDatabase.getSpecificDogDao();
-        mWithClauseDao = mDatabase.getWithClauseDao();
-        mFunnyNamedDao = mDatabase.getFunnyNamedDao();
-        mRawDao = mDatabase.getRawDao();
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestUtil.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestUtil.java
deleted file mode 100644
index d5309b3..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/TestUtil.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import android.arch.persistence.room.integration.testapp.vo.Address;
-import android.arch.persistence.room.integration.testapp.vo.Coordinates;
-import android.arch.persistence.room.integration.testapp.vo.Pet;
-import android.arch.persistence.room.integration.testapp.vo.School;
-import android.arch.persistence.room.integration.testapp.vo.Toy;
-import android.arch.persistence.room.integration.testapp.vo.User;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
-public class TestUtil {
-    public static User[] createUsersArray(int... ids) {
-        User[] result = new User[ids.length];
-        for (int i = 0; i < ids.length; i++) {
-            result[i] = createUser(ids[i]);
-        }
-        return result;
-    }
-
-    public static List<User> createUsersList(int... ids) {
-        List<User> result = new ArrayList<>();
-        for (int id : ids) {
-            result.add(createUser(id));
-        }
-        return result;
-    }
-
-    public static User createUser(int id) {
-        User user = new User();
-        user.setId(id);
-        user.setName(UUID.randomUUID().toString());
-        user.setLastName(UUID.randomUUID().toString());
-        user.setAge((int) (10 + Math.random() * 50));
-        user.setCustomField(UUID.randomUUID().toString());
-        user.setBirthday(new Date());
-        return user;
-    }
-
-    public static Pet createPet(int id) {
-        Pet pet = new Pet();
-        pet.setPetId(id);
-        pet.setName(UUID.randomUUID().toString());
-        pet.setAdoptionDate(new Date());
-        return pet;
-    }
-
-    public static Toy createToyForPet(Pet pet, int toyId) {
-        Toy toy = new Toy();
-        toy.setName("toy " + toyId);
-        toy.setId(toyId);
-        toy.setPetId(pet.getPetId());
-        return toy;
-    }
-
-    public static Pet[] createPetsForUser(int uid, int petStartId, int count) {
-        Pet[] pets = new Pet[count];
-        for (int i = 0; i < count; i++) {
-            Pet pet = createPet(petStartId++);
-            pet.setUserId(uid);
-            pets[i] = pet;
-        }
-        return pets;
-    }
-
-    public static School createSchool(int id, int managerId) {
-        School school = new School();
-        school.setId(id);
-        school.setName(UUID.randomUUID().toString());
-        school.setManager(createUser(managerId));
-        school.setAddress(createAddress());
-        return school;
-    }
-
-    private static Address createAddress() {
-        Address address = new Address();
-        address.setCoordinates(createCoordinates());
-        address.setPostCode((int) (Math.random() * 1000 + 1000));
-        address.setState(UUID.randomUUID().toString().substring(0, 2));
-        address.setStreet(UUID.randomUUID().toString());
-        return address;
-    }
-
-    private static Coordinates createCoordinates() {
-        Coordinates coordinates = new Coordinates();
-        coordinates.lat = Math.random();
-        coordinates.lng = Math.random();
-        return coordinates;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
deleted file mode 100644
index 9209638..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.hamcrest.CoreMatchers.is;
-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;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-public class WithClauseTest extends TestDatabaseTest{
-    @Test
-    public void noSourceOfData() {
-        @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
-        List<Integer> expected = Arrays.asList(1);
-        List<Integer> actual = mWithClauseDao.getFactorials(0);
-        assertThat(expected, is(actual));
-
-        expected = Arrays.asList(1, 1, 2, 6, 24);
-        actual = mWithClauseDao.getFactorials(4);
-        assertThat(expected, is(actual));
-    }
-
-    @Test
-    public void sourceOfData() {
-        List<String> expected = new ArrayList<>();
-        List<String> actual = mWithClauseDao.getUsersWithFactorialIds(0);
-        assertThat(expected, is(actual));
-
-        User user = new User();
-        user.setId(0);
-        user.setName("Zero");
-        mUserDao.insert(user);
-        actual = mWithClauseDao.getUsersWithFactorialIds(0);
-        assertThat(expected, is(actual));
-
-        user = new User();
-        user.setId(1);
-        user.setName("One");
-        mUserDao.insert(user);
-        expected.add("One");
-        actual = mWithClauseDao.getUsersWithFactorialIds(0);
-        assertThat(expected, is(actual));
-
-        user = new User();
-        user.setId(6);
-        user.setName("Six");
-        mUserDao.insert(user);
-        actual = mWithClauseDao.getUsersWithFactorialIds(0);
-        assertThat(expected, is(actual));
-
-        actual = mWithClauseDao.getUsersWithFactorialIds(3);
-        expected.add("Six");
-        assertThat(expected, is(actual));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WriteAheadLoggingTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WriteAheadLoggingTest.java
deleted file mode 100644
index 5bc01ce..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/WriteAheadLoggingTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright 2018 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.equalTo;
-import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalToIgnoringCase;
-import static org.hamcrest.Matchers.hasSize;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.Observer;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.room.InvalidationTracker;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-import android.arch.persistence.room.integration.testapp.dao.UserDao;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.content.Context;
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-@SdkSuppress(minSdkVersion = 16)
-public class WriteAheadLoggingTest {
-
-    private static final String DATABASE_NAME = "wal.db";
-    private TestDatabase mDatabase;
-
-    @Before
-    public void openDatabase() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        context.deleteDatabase(DATABASE_NAME);
-        mDatabase = Room.databaseBuilder(context, TestDatabase.class, DATABASE_NAME)
-                .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
-                .build();
-    }
-
-    @After
-    public void closeDatabase() {
-        mDatabase.close();
-        Context context = InstrumentationRegistry.getTargetContext();
-        context.deleteDatabase(DATABASE_NAME);
-    }
-
-    @Test
-    public void checkJournalMode() {
-        Cursor c = null;
-        try {
-            SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
-            c = db.query("PRAGMA journal_mode");
-            c.moveToFirst();
-            String journalMode = c.getString(0);
-            assertThat(journalMode, is(equalToIgnoringCase("wal")));
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-    }
-
-    @Test
-    public void disableWal() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        mDatabase.close();
-        mDatabase = Room.databaseBuilder(context, TestDatabase.class, DATABASE_NAME)
-                .setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
-                .build();
-        Cursor c = null;
-        try {
-            SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
-            c = db.query("PRAGMA journal_mode");
-            c.moveToFirst();
-            String journalMode = c.getString(0);
-            assertThat(journalMode, is(not(equalToIgnoringCase("wal"))));
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-    }
-
-    @Test
-    public void observeLiveData() {
-        UserDao dao = mDatabase.getUserDao();
-        LiveData<User> user1 = dao.liveUserById(1);
-        Observer<User> observer = startObserver(user1);
-        dao.insert(TestUtil.createUser(1));
-        verify(observer, timeout(3000).atLeastOnce())
-                .onChanged(argThat(user -> user != null && user.getId() == 1));
-        stopObserver(user1, observer);
-    }
-
-    @Test
-    public void observeLiveDataWithTransaction() {
-        UserDao dao = mDatabase.getUserDao();
-        LiveData<User> user1 = dao.liveUserByIdInTransaction(1);
-        Observer<User> observer = startObserver(user1);
-        dao.insert(TestUtil.createUser(1));
-        verify(observer, timeout(3000).atLeastOnce())
-                .onChanged(argThat(user -> user != null && user.getId() == 1));
-        stopObserver(user1, observer);
-    }
-
-    @Test
-    public void parallelWrites() throws InterruptedException, ExecutionException {
-        int numberOfThreads = 10;
-        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
-        ArrayList<Future<Boolean>> futures = new ArrayList<>();
-        for (int i = 0; i < numberOfThreads; i++) {
-            final int id = i + 1;
-            futures.add(i, executor.submit(() -> {
-                User user = TestUtil.createUser(id);
-                user.setName("user" + id);
-                mDatabase.getUserDao().insert(user);
-                return true;
-            }));
-        }
-        LiveData<List<User>> usersList = mDatabase.getUserDao().liveUsersListByName("user");
-        Observer<List<User>> observer = startObserver(usersList);
-        for (Future future : futures) {
-            assertThat(future.get(), is(true));
-        }
-        verify(observer, timeout(3000).atLeastOnce())
-                .onChanged(argThat(users -> users != null && users.size() == numberOfThreads));
-        stopObserver(usersList, observer);
-    }
-
-    @Test
-    public void readInBackground() throws InterruptedException, ExecutionException {
-        final UserDao dao = mDatabase.getUserDao();
-        final User user1 = TestUtil.createUser(1);
-        dao.insert(user1);
-        try {
-            mDatabase.beginTransaction();
-            dao.delete(user1);
-            ExecutorService executor = Executors.newSingleThreadExecutor();
-            Future<?> future = executor.submit(() ->
-                    assertThat(dao.load(1), is(equalTo(user1))));
-            future.get();
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            mDatabase.endTransaction();
-        }
-        assertThat(dao.count(), is(0));
-    }
-
-    @Test
-    @LargeTest
-    public void observeInvalidationInBackground() throws InterruptedException, ExecutionException {
-        final UserDao dao = mDatabase.getUserDao();
-        final User user1 = TestUtil.createUser(1);
-        final CountDownLatch observerRegistered = new CountDownLatch(1);
-        final CountDownLatch onInvalidatedCalled = new CountDownLatch(1);
-        dao.insert(user1);
-        Future future;
-        try {
-            mDatabase.beginTransaction();
-            dao.delete(user1);
-            future = Executors.newSingleThreadExecutor().submit(() -> {
-                // Adding this observer will be blocked by the surrounding transaction.
-                mDatabase.getInvalidationTracker().addObserver(
-                        new InvalidationTracker.Observer("User") {
-                            @Override
-                            public void onInvalidated(@NonNull Set<String> tables) {
-                                onInvalidatedCalled.countDown(); // This should not happen
-                            }
-                        });
-                observerRegistered.countDown();
-            });
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            assertThat(observerRegistered.getCount(), is(1L));
-            mDatabase.endTransaction();
-        }
-        assertThat(dao.count(), is(0));
-        assertThat(observerRegistered.await(3000, TimeUnit.MILLISECONDS), is(true));
-        future.get();
-        assertThat(onInvalidatedCalled.await(500, TimeUnit.MILLISECONDS), is(false));
-    }
-
-    @Test
-    public void invalidation() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        mDatabase.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("User") {
-            @Override
-            public void onInvalidated(@NonNull Set<String> tables) {
-                assertThat(tables, hasSize(1));
-                assertThat(tables, hasItem("User"));
-                latch.countDown();
-            }
-        });
-        mDatabase.getUserDao().insert(TestUtil.createUser(1));
-        assertThat(latch.await(3000, TimeUnit.MILLISECONDS), is(true));
-    }
-
-    private static <T> Observer<T> startObserver(LiveData<T> liveData) {
-        @SuppressWarnings("unchecked")
-        Observer<T> observer = mock(Observer.class);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
-                liveData.observeForever(observer));
-        return observer;
-    }
-
-    private static <T> void stopObserver(LiveData<T> liveData, Observer<T> observer) {
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
-                liveData.removeObserver(observer));
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Address.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Address.java
deleted file mode 100644
index 46f3bb6..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Address.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Embedded;
-
-public class Address {
-    @ColumnInfo(name = "street")
-    private String mStreet;
-    @ColumnInfo(name = "state")
-    private String mState;
-    @ColumnInfo(name = "post_code")
-    private int mPostCode;
-    @Embedded
-    private Coordinates mCoordinates;
-
-    public String getStreet() {
-        return mStreet;
-    }
-
-    public void setStreet(String street) {
-        mStreet = street;
-    }
-
-    public String getState() {
-        return mState;
-    }
-
-    public void setState(String state) {
-        mState = state;
-    }
-
-    public int getPostCode() {
-        return mPostCode;
-    }
-
-    public void setPostCode(int postCode) {
-        mPostCode = postCode;
-    }
-
-    public Coordinates getCoordinates() {
-        return mCoordinates;
-    }
-
-    public void setCoordinates(Coordinates coordinates) {
-        mCoordinates = coordinates;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Address address = (Address) o;
-
-        if (mPostCode != address.mPostCode) return false;
-        if (mStreet != null ? !mStreet.equals(address.mStreet) : address.mStreet != null) {
-            return false;
-        }
-        if (mState != null ? !mState.equals(address.mState) : address.mState != null) {
-            return false;
-        }
-        return mCoordinates != null ? mCoordinates.equals(address.mCoordinates)
-                : address.mCoordinates == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mStreet != null ? mStreet.hashCode() : 0;
-        result = 31 * result + (mState != null ? mState.hashCode() : 0);
-        result = 31 * result + mPostCode;
-        result = 31 * result + (mCoordinates != null ? mCoordinates.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/AvgWeightByAge.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/AvgWeightByAge.java
deleted file mode 100644
index 4d22f13..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/AvgWeightByAge.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Ignore;
-
-@SuppressWarnings("unused")
-public class AvgWeightByAge {
-
-    private int mAge;
-
-    @ColumnInfo(name = "AVG(mWeight)")
-    private float mWeight;
-
-    public AvgWeightByAge() {
-    }
-
-    @Ignore
-    public AvgWeightByAge(int age, float weight) {
-        mAge = age;
-        mWeight = weight;
-    }
-
-    public int getAge() {
-        return mAge;
-    }
-
-    public void setAge(int age) {
-        mAge = age;
-    }
-
-    public float getWeight() {
-        return mWeight;
-    }
-
-    public void setWeight(float weight) {
-        mWeight = weight;
-    }
-
-    @Override
-    public String toString() {
-        return "AvgWeightByAge{"
-                + "mAge=" + mAge
-                + ", mWeight=" + mWeight
-                + '}';
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        AvgWeightByAge that = (AvgWeightByAge) o;
-
-        //noinspection SimplifiableIfStatement
-        if (mAge != that.mAge) {
-            return false;
-        }
-        return Float.compare(that.mWeight, mWeight) == 0;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mAge;
-        result = 31 * result + (mWeight != +0.0f ? Float.floatToIntBits(mWeight) : 0);
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/BlobEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/BlobEntity.java
deleted file mode 100644
index 134afc7..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/BlobEntity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-
-@Entity
-public class BlobEntity {
-    @PrimaryKey
-    public long id;
-    public byte[] content;
-
-    public BlobEntity(long id, byte[] content) {
-        this.id = id;
-        this.content = content;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Coordinates.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Coordinates.java
deleted file mode 100644
index e8cfd06..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Coordinates.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-public class Coordinates {
-    public double lat;
-    public double lng;
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Coordinates that = (Coordinates) o;
-
-        if (Double.compare(that.lat, lat) != 0) return false;
-        return Double.compare(that.lng, lng) == 0;
-    }
-
-    @Override
-    public int hashCode() {
-        int result;
-        long temp;
-        temp = Double.doubleToLongBits(lat);
-        result = (int) (temp ^ (temp >>> 32));
-        temp = Double.doubleToLongBits(lng);
-        result = 31 * result + (int) (temp ^ (temp >>> 32));
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Day.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Day.java
deleted file mode 100644
index e02b91c..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Day.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 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;
-
-public enum Day {
-    MONDAY,
-    TUESDAY,
-    WEDNESDAY,
-    THURSDAY,
-    FRIDAY,
-    SATURDAY,
-    SUNDAY
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/EmbeddedUserAndAllPets.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/EmbeddedUserAndAllPets.java
deleted file mode 100644
index ca82bfe..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/EmbeddedUserAndAllPets.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Embedded;
-
-public class EmbeddedUserAndAllPets {
-    @Embedded
-    UserAndAllPets mUserAndAllPets;
-
-    public UserAndAllPets getUserAndAllPets() {
-        return mUserAndAllPets;
-    }
-
-    public void setUserAndAllPets(UserAndAllPets userAndAllPets) {
-        this.mUserAndAllPets = userAndAllPets;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java
deleted file mode 100644
index 20f3c21..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntAutoIncPKeyEntity.java
deleted file mode 100644
index 3392649..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntAutoIncPKeyEntity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-
-@Entity
-public class IntAutoIncPKeyEntity {
-    @PrimaryKey(autoGenerate = true)
-    public int pKey;
-    public String data;
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java
deleted file mode 100644
index 636ffd9..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-
-@Entity
-public class IntegerAutoIncPKeyEntity {
-    @PrimaryKey(autoGenerate = true)
-    public Integer pKey;
-    public String data;
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
deleted file mode 100644
index cae1843..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/NameAndLastName.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/NameAndLastName.java
deleted file mode 100644
index a6e8223..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/NameAndLastName.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 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;
-
-
-public class NameAndLastName {
-    private String mName;
-    private String mLastName;
-
-    public NameAndLastName(String name, String lastName) {
-        mName = name;
-        mLastName = lastName;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public String getLastName() {
-        return mLastName;
-    }
-
-    @SuppressWarnings("SimplifiableIfStatement")
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        NameAndLastName that = (NameAndLastName) o;
-
-        if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
-        return mLastName != null ? mLastName.equals(that.mLastName) : that.mLastName == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mName != null ? mName.hashCode() : 0;
-        result = 31 * result + (mLastName != null ? mLastName.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
deleted file mode 100644
index 895a35a..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.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/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Pet.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Pet.java
deleted file mode 100644
index cc7549f..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Pet.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-
-import java.util.Date;
-
-@Entity
-public class Pet {
-    @PrimaryKey
-    private int mPetId;
-    private int mUserId;
-    @ColumnInfo(name = "mPetName")
-    private String mName;
-
-    private Date mAdoptionDate;
-
-    public int getPetId() {
-        return mPetId;
-    }
-
-    public void setPetId(int petId) {
-        mPetId = petId;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public void setName(String name) {
-        mName = name;
-    }
-
-    public int getUserId() {
-        return mUserId;
-    }
-
-    public void setUserId(int userId) {
-        mUserId = userId;
-    }
-
-    public Date getAdoptionDate() {
-        return mAdoptionDate;
-    }
-
-    public void setAdoptionDate(Date adoptionDate) {
-        mAdoptionDate = adoptionDate;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Pet pet = (Pet) o;
-
-        if (mPetId != pet.mPetId) return false;
-        if (mUserId != pet.mUserId) return false;
-        if (mName != null ? !mName.equals(pet.mName) : pet.mName != null) return false;
-        return mAdoptionDate != null ? mAdoptionDate.equals(pet.mAdoptionDate)
-                : pet.mAdoptionDate == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mPetId;
-        result = 31 * result + mUserId;
-        result = 31 * result + (mName != null ? mName.hashCode() : 0);
-        result = 31 * result + (mAdoptionDate != null ? mAdoptionDate.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "Pet{"
-                + "mPetId=" + mPetId
-                + ", mUserId=" + mUserId
-                + ", mName='" + mName + '\''
-                + ", mAdoptionDate=" + mAdoptionDate
-                + '}';
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetAndToys.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetAndToys.java
deleted file mode 100644
index 69fb591..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetAndToys.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Embedded;
-import android.arch.persistence.room.Relation;
-
-import java.util.List;
-
-public class PetAndToys {
-    @Embedded
-    public Pet pet;
-    @Relation(parentColumn = "mPetId", entityColumn = "mPetId")
-    public List<Toy> toys;
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetCouple.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
deleted file mode 100644
index e5208ed..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Embedded;
-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;
-    @Embedded(prefix = "female_")
-    private Pet mFemale;
-
-    public Pet getFemale() {
-        return mFemale;
-    }
-
-    public void setFemale(Pet female) {
-        mFemale = female;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        PetCouple petCouple = (PetCouple) o;
-
-        if (male != null ? !male.equals(petCouple.male) : petCouple.male != null) return false;
-        return mFemale != null ? mFemale.equals(petCouple.mFemale) : petCouple.mFemale == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = male != null ? male.hashCode() : 0;
-        result = 31 * result + (mFemale != null ? mFemale.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetWithToyIds.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetWithToyIds.java
deleted file mode 100644
index 005afb1..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetWithToyIds.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 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.Embedded;
-import android.arch.persistence.room.Ignore;
-import android.arch.persistence.room.Relation;
-
-import java.util.List;
-
-public class PetWithToyIds {
-    @Embedded
-    public final Pet pet;
-    @Relation(parentColumn = "mPetId", entityColumn = "mPetId", projection = "mId",
-            entity = Toy.class)
-    public List<Integer> toyIds;
-
-    // for the relation
-    public PetWithToyIds(Pet pet) {
-        this.pet = pet;
-    }
-
-    @Ignore
-    public PetWithToyIds(Pet pet, List<Integer> toyIds) {
-        this.pet = pet;
-        this.toyIds = toyIds;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        PetWithToyIds that = (PetWithToyIds) o;
-
-        if (pet != null ? !pet.equals(that.pet) : that.pet != null) return false;
-        return toyIds != null ? toyIds.equals(that.toyIds) : that.toyIds == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = pet != null ? pet.hashCode() : 0;
-        result = 31 * result + (toyIds != null ? toyIds.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "PetWithToyIds{"
-                + "pet=" + pet
-                + ", toyIds=" + toyIds
-                + '}';
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetsToys.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetsToys.java
deleted file mode 100644
index 16bddd6..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/PetsToys.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Relation;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PetsToys {
-    public int petId;
-    @Relation(parentColumn = "petId", entityColumn = "mPetId")
-    public List<Toy> toys;
-
-    public PetsToys() {
-        toys = new ArrayList<>();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        PetsToys petsToys = (PetsToys) o;
-
-        if (petId != petsToys.petId) {
-            return false;
-        }
-        return toys != null ? toys.equals(petsToys.toys) : petsToys.toys == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = petId;
-        result = 31 * result + (toys != null ? toys.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Product.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Product.java
deleted file mode 100644
index a395aea..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Product.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-import android.support.annotation.NonNull;
-
-@Entity(tableName = "products")
-public class Product {
-
-    @PrimaryKey(autoGenerate = true)
-    public final int id;
-
-    @NonNull
-    public final String name;
-
-    public Product(int id, @NonNull String name) {
-        this.id = id;
-        this.name = name;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/School.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/School.java
deleted file mode 100644
index f0426f1..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/School.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Embedded;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-
-@Entity
-public class School {
-    @PrimaryKey
-    private int mId;
-    private String mName;
-    @Embedded(prefix = "address_")
-    public Address address;
-
-    @Embedded(prefix = "manager_")
-    private User mManager;
-
-    public int getId() {
-        return mId;
-    }
-
-    public void setId(int id) {
-        mId = id;
-    }
-
-    public Address getAddress() {
-        return address;
-    }
-
-    public void setAddress(Address address) {
-        this.address = address;
-    }
-
-    public User getManager() {
-        return mManager;
-    }
-
-    public void setManager(User manager) {
-        mManager = manager;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public void setName(String name) {
-        mName = name;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        School school = (School) o;
-
-        if (mId != school.mId) {
-            return false;
-        }
-        if (mName != null ? !mName.equals(school.mName) : school.mName != null) {
-            return false;
-        }
-        if (address != null ? !address.equals(school.address) : school.address != null) {
-            return false;
-        }
-        return mManager != null ? mManager.equals(school.mManager) : school.mManager == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mId;
-        result = 31 * result + (mName != null ? mName.hashCode() : 0);
-        result = 31 * result + (address != null ? address.hashCode() : 0);
-        result = 31 * result + (mManager != null ? mManager.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/SchoolRef.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/SchoolRef.java
deleted file mode 100644
index bbf0cb4..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/SchoolRef.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-public class SchoolRef extends School {
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Toy.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Toy.java
deleted file mode 100644
index 5198c2d..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/Toy.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-
-/**
- * The toys of a pet.
- */
-@Entity
-public class Toy {
-    @PrimaryKey(autoGenerate = true)
-    private int mId;
-    private String mName;
-    private int mPetId;
-
-    public int getId() {
-        return mId;
-    }
-
-    public void setId(int id) {
-        mId = id;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public void setName(String name) {
-        mName = name;
-    }
-
-    public int getPetId() {
-        return mPetId;
-    }
-
-    public void setPetId(int petId) {
-        mPetId = petId;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Toy toy = (Toy) o;
-
-        if (mId != toy.mId) return false;
-        if (mPetId != toy.mPetId) return false;
-        return mName != null ? mName.equals(toy.mName) : toy.mName == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mId;
-        result = 31 * result + (mName != null ? mName.hashCode() : 0);
-        result = 31 * result + mPetId;
-        return result;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/User.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/User.java
deleted file mode 100644
index a615819..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/User.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.TypeConverters;
-import android.arch.persistence.room.integration.testapp.TestDatabase;
-
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
-
-@Entity
-@TypeConverters({TestDatabase.Converters.class})
-public class User {
-
-    @PrimaryKey
-    private int mId;
-
-    private String mName;
-
-    private String mLastName;
-
-    private int mAge;
-
-    private boolean mAdmin;
-
-    private float mWeight;
-
-    private Date mBirthday;
-
-    @ColumnInfo(name = "custommm", collate = ColumnInfo.NOCASE)
-    private String mCustomField;
-
-    // bit flags
-    private Set<Day> mWorkDays = new HashSet<>();
-
-    public int getId() {
-        return mId;
-    }
-
-    public void setId(int id) {
-        this.mId = id;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public void setName(String name) {
-        this.mName = name;
-    }
-
-    public String getLastName() {
-        return mLastName;
-    }
-
-    public void setLastName(String lastName) {
-        this.mLastName = lastName;
-    }
-
-    public int getAge() {
-        return mAge;
-    }
-
-    public void setAge(int age) {
-        this.mAge = age;
-    }
-
-    public boolean isAdmin() {
-        return mAdmin;
-    }
-
-    public void setAdmin(boolean admin) {
-        mAdmin = admin;
-    }
-
-    public float getWeight() {
-        return mWeight;
-    }
-
-    public void setWeight(float weight) {
-        mWeight = weight;
-    }
-
-    public Date getBirthday() {
-        return mBirthday;
-    }
-
-    public void setBirthday(Date birthday) {
-        mBirthday = birthday;
-    }
-
-    public String getCustomField() {
-        return mCustomField;
-    }
-
-    public void setCustomField(String customField) {
-        mCustomField = customField;
-    }
-
-    public Set<Day> getWorkDays() {
-        return mWorkDays;
-    }
-
-    public void setWorkDays(
-            Set<Day> workDays) {
-        mWorkDays = workDays;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        User user = (User) o;
-
-        if (mId != user.mId) return false;
-        if (mAge != user.mAge) return false;
-        if (mAdmin != user.mAdmin) return false;
-        if (Float.compare(user.mWeight, mWeight) != 0) return false;
-        if (mName != null ? !mName.equals(user.mName) : user.mName != null) return false;
-        if (mLastName != null ? !mLastName.equals(user.mLastName) : user.mLastName != null) {
-            return false;
-        }
-        if (mBirthday != null ? !mBirthday.equals(user.mBirthday) : user.mBirthday != null) {
-            return false;
-        }
-        if (mCustomField != null ? !mCustomField.equals(user.mCustomField)
-                : user.mCustomField != null) {
-            return false;
-        }
-        return mWorkDays != null ? mWorkDays.equals(user.mWorkDays) : user.mWorkDays == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mId;
-        result = 31 * result + (mName != null ? mName.hashCode() : 0);
-        result = 31 * result + (mLastName != null ? mLastName.hashCode() : 0);
-        result = 31 * result + mAge;
-        result = 31 * result + (mAdmin ? 1 : 0);
-        result = 31 * result + (mWeight != +0.0f ? Float.floatToIntBits(mWeight) : 0);
-        result = 31 * result + (mBirthday != null ? mBirthday.hashCode() : 0);
-        result = 31 * result + (mCustomField != null ? mCustomField.hashCode() : 0);
-        result = 31 * result + (mWorkDays != null ? mWorkDays.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "User{"
-                + "mId=" + mId
-                + ", mName='" + mName + '\''
-                + ", mLastName='" + mLastName + '\''
-                + ", mAge=" + mAge
-                + ", mAdmin=" + mAdmin
-                + ", mWeight=" + mWeight
-                + ", mBirthday=" + mBirthday
-                + ", mCustomField='" + mCustomField + '\''
-                + ", mWorkDays=" + mWorkDays
-                + '}';
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndAllPets.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndAllPets.java
deleted file mode 100644
index 24a0710..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndAllPets.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Embedded;
-import android.arch.persistence.room.Relation;
-
-import java.util.List;
-
-public class UserAndAllPets {
-    @Embedded
-    public User user;
-    @Relation(parentColumn = "mId", entityColumn = "mUserId")
-    public List<Pet> pets;
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPet.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPet.java
deleted file mode 100644
index 628e9bf..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPet.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-import android.arch.persistence.room.Embedded;
-
-public class UserAndPet {
-    @Embedded
-    private User mUser;
-    @Embedded
-    private Pet mPet;
-
-    public User getUser() {
-        return mUser;
-    }
-
-    public void setUser(User user) {
-        mUser = user;
-    }
-
-    public Pet getPet() {
-        return mPet;
-    }
-
-    public void setPet(Pet pet) {
-        mPet = pet;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetAdoptionDates.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetAdoptionDates.java
deleted file mode 100644
index 28d2495..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetAdoptionDates.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 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.Embedded;
-import android.arch.persistence.room.Ignore;
-import android.arch.persistence.room.Relation;
-
-import java.util.Date;
-import java.util.List;
-
-public class UserAndPetAdoptionDates {
-    @Embedded
-    public final User user;
-    @Relation(entity = Pet.class,
-            parentColumn = "mId",
-            entityColumn = "mUserId",
-            projection = "mAdoptionDate")
-    public List<Date> petAdoptionDates;
-
-    public UserAndPetAdoptionDates(User user) {
-        this.user = user;
-    }
-
-    @Ignore
-    public UserAndPetAdoptionDates(User user, List<Date> petAdoptionDates) {
-        this.user = user;
-        this.petAdoptionDates = petAdoptionDates;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        UserAndPetAdoptionDates that = (UserAndPetAdoptionDates) o;
-
-        if (user != null ? !user.equals(that.user) : that.user != null) return false;
-        return petAdoptionDates != null ? petAdoptionDates.equals(that.petAdoptionDates)
-                : that.petAdoptionDates == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = user != null ? user.hashCode() : 0;
-        result = 31 * result + (petAdoptionDates != null ? petAdoptionDates.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "UserAndPetAdoptionDates{"
-                + "user=" + user
-                + ", petAdoptionDates=" + petAdoptionDates
-                + '}';
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetNonNull.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetNonNull.java
deleted file mode 100644
index 8739bd0..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserAndPetNonNull.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Embedded;
-import android.support.annotation.NonNull;
-
-public class UserAndPetNonNull {
-    @Embedded
-    private User mUser;
-    @Embedded
-    @NonNull
-    private Pet mPet;
-
-    public User getUser() {
-        return mUser;
-    }
-
-    public void setUser(User user) {
-        mUser = user;
-    }
-
-    public Pet getPet() {
-        return mPet;
-    }
-
-    public void setPet(Pet pet) {
-        mPet = pet;
-    }
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserIdAndPetNames.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserIdAndPetNames.java
deleted file mode 100644
index 444431e..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserIdAndPetNames.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Relation;
-
-import java.util.List;
-
-/**
- * Same as Pet class but only keeps name and user id
- */
-public class UserIdAndPetNames {
-    @ColumnInfo(name = "mId")
-    public int userId;
-    @Relation(entity = Pet.class, parentColumn = "mId", entityColumn = "mUserId",
-            projection = "mPetName")
-    public List<String> names;
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserWithPetsAndToys.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserWithPetsAndToys.java
deleted file mode 100644
index 01c9bed..0000000
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/vo/UserWithPetsAndToys.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.vo;
-
-import android.arch.persistence.room.Embedded;
-import android.arch.persistence.room.Relation;
-
-import java.util.List;
-
-public class UserWithPetsAndToys {
-    @Embedded
-    public User user;
-    @Relation(entity = Pet.class, parentColumn = "mId", entityColumn = "mUserId")
-    public List<PetAndToys> pets;
-}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/InvalidationTrackerTrojan.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/InvalidationTrackerTrojan.java
new file mode 100644
index 0000000..2e52952
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/InvalidationTrackerTrojan.java
@@ -0,0 +1,26 @@
+/*
+ * 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 androidx.room;
+
+/**
+ * Trojan class to be able to assert internal state.
+ */
+public class InvalidationTrackerTrojan {
+    public static int countObservers(InvalidationTracker tracker) {
+        return tracker.mObserverMap.size();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/PKeyTestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/PKeyTestDatabase.java
new file mode 100644
index 0000000..233f270
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/PKeyTestDatabase.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 androidx.room.integration.testapp;
+
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.RoomDatabase;
+import androidx.room.integration.testapp.vo.IntAutoIncPKeyEntity;
+import androidx.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import androidx.room.integration.testapp.vo.IntegerPKeyEntity;
+import androidx.room.integration.testapp.vo.ObjectPKeyEntity;
+
+import java.util.List;
+
+@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
+    public interface IntPKeyDao {
+        @Insert
+        void insertMe(IntAutoIncPKeyEntity... items);
+        @Insert
+        long insertAndGetId(IntAutoIncPKeyEntity item);
+
+        @Insert
+        long[] insertAndGetIds(IntAutoIncPKeyEntity... item);
+
+        @Query("select * from IntAutoIncPKeyEntity WHERE pKey = :key")
+        IntAutoIncPKeyEntity getMe(int key);
+
+        @Query("select data from IntAutoIncPKeyEntity WHERE pKey IN(:ids)")
+        List<String> loadDataById(long... ids);
+    }
+
+    @Dao
+    public interface IntegerAutoIncPKeyDao {
+        @Insert
+        void insertMe(IntegerAutoIncPKeyEntity item);
+
+        @Query("select * from IntegerAutoIncPKeyEntity WHERE pKey = :key")
+        IntegerAutoIncPKeyEntity getMe(int key);
+
+        @Insert
+        long insertAndGetId(IntegerAutoIncPKeyEntity item);
+
+        @Insert
+        long[] insertAndGetIds(IntegerAutoIncPKeyEntity... item);
+
+        @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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
new file mode 100644
index 0000000..f993978
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp;
+
+import androidx.room.Database;
+import androidx.room.RoomDatabase;
+import androidx.room.TypeConverter;
+import androidx.room.TypeConverters;
+import androidx.room.integration.testapp.dao.BlobEntityDao;
+import androidx.room.integration.testapp.dao.FunnyNamedDao;
+import androidx.room.integration.testapp.dao.PetCoupleDao;
+import androidx.room.integration.testapp.dao.PetDao;
+import androidx.room.integration.testapp.dao.ProductDao;
+import androidx.room.integration.testapp.dao.RawDao;
+import androidx.room.integration.testapp.dao.SchoolDao;
+import androidx.room.integration.testapp.dao.SpecificDogDao;
+import androidx.room.integration.testapp.dao.ToyDao;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.dao.UserPetDao;
+import androidx.room.integration.testapp.dao.WithClauseDao;
+import androidx.room.integration.testapp.vo.BlobEntity;
+import androidx.room.integration.testapp.vo.Day;
+import androidx.room.integration.testapp.vo.FunnyNamedEntity;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.PetCouple;
+import androidx.room.integration.testapp.vo.Product;
+import androidx.room.integration.testapp.vo.School;
+import androidx.room.integration.testapp.vo.Toy;
+import androidx.room.integration.testapp.vo.User;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+@Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
+        BlobEntity.class, Product.class, FunnyNamedEntity.class},
+        version = 1, exportSchema = false)
+@TypeConverters(TestDatabase.Converters.class)
+public abstract class TestDatabase extends RoomDatabase {
+    public abstract UserDao getUserDao();
+    public abstract PetDao getPetDao();
+    public abstract UserPetDao getUserPetDao();
+    public abstract SchoolDao getSchoolDao();
+    public abstract PetCoupleDao getPetCoupleDao();
+    public abstract ToyDao getToyDao();
+    public abstract BlobEntityDao getBlobEntityDao();
+    public abstract ProductDao getProductDao();
+    public abstract SpecificDogDao getSpecificDogDao();
+    public abstract WithClauseDao getWithClauseDao();
+    public abstract FunnyNamedDao getFunnyNamedDao();
+    public abstract RawDao getRawDao();
+
+    @SuppressWarnings("unused")
+    public static class Converters {
+        @TypeConverter
+        public Date fromTimestamp(Long value) {
+            return value == null ? null : new Date(value);
+        }
+
+        @TypeConverter
+        public Long dateToTimestamp(Date date) {
+            if (date == null) {
+                return null;
+            } else {
+                return date.getTime();
+            }
+        }
+
+        @TypeConverter
+        public Set<Day> decomposeDays(int flags) {
+            Set<Day> result = new HashSet<>();
+            for (Day day : Day.values()) {
+                if ((flags & (1 << day.ordinal())) != 0) {
+                    result.add(day);
+                }
+            }
+            return result;
+        }
+
+        @TypeConverter
+        public int composeDays(Set<Day> days) {
+            int result = 0;
+            for (Day day : days) {
+                result |= 1 << day.ordinal();
+            }
+            return result;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/BlobEntityDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/BlobEntityDao.java
new file mode 100644
index 0000000..3c582fd
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/BlobEntityDao.java
@@ -0,0 +1,40 @@
+/*
+ * 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 androidx.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.integration.testapp.vo.BlobEntity;
+
+import java.util.List;
+
+@Dao
+public interface BlobEntityDao {
+
+    @Insert
+    void insert(BlobEntity item);
+
+    @Query("SELECT * FROM BlobEntity")
+    List<BlobEntity> selectAll();
+
+    @Query("SELECT content FROM BlobEntity WHERE id = :id")
+    byte[] getContent(long id);
+
+    @Query("UPDATE BlobEntity SET content = :content WHERE id = :id")
+    void updateContent(long id, byte[] content);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/FunnyNamedDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/FunnyNamedDao.java
new file mode 100644
index 0000000..e6b57df
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/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 androidx.room.integration.testapp.dao;
+
+import static androidx.room.integration.testapp.vo.FunnyNamedEntity.COLUMN_ID;
+import static androidx.room.integration.testapp.vo.FunnyNamedEntity.TABLE_NAME;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.Update;
+import androidx.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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/PetCoupleDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/PetCoupleDao.java
new file mode 100644
index 0000000..e027d77
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/PetCoupleDao.java
@@ -0,0 +1,33 @@
+/*
+ * 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 androidx.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.integration.testapp.vo.PetCouple;
+
+import java.util.List;
+
+@Dao
+public interface PetCoupleDao {
+    @Insert
+    void insert(PetCouple couple);
+
+    @Query("SELECT * FROM PetCouple")
+    List<PetCouple> loadAll();
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/PetDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/PetDao.java
new file mode 100644
index 0000000..ce63814
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/PetDao.java
@@ -0,0 +1,58 @@
+/*
+ * 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 androidx.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Transaction;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.PetWithToyIds;
+
+import java.util.List;
+
+@Dao
+public interface PetDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertOrReplace(Pet... pets);
+
+    @Insert
+    void insertAll(Pet[] pets);
+
+    @Query("SELECT COUNT(*) FROM Pet")
+    int count();
+
+    @Query("SELECT * FROM Pet ORDER BY Pet.mPetId ASC")
+    List<PetWithToyIds> allPetsWithToyIds();
+
+    @Delete
+    void delete(Pet pet);
+
+    @Query("SELECT mPetId FROM Pet")
+    int[] allIds();
+
+    @Transaction
+    default void deleteAndInsert(Pet oldPet, Pet newPet, boolean shouldFail) {
+        delete(oldPet);
+        if (shouldFail) {
+            throw new RuntimeException();
+        }
+        insertOrReplace(newPet);
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/ProductDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/ProductDao.java
new file mode 100644
index 0000000..47b1a3b
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/ProductDao.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 androidx.room.integration.testapp.dao;
+
+import androidx.annotation.NonNull;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.integration.testapp.vo.Product;
+
+@Dao
+public interface ProductDao {
+
+    @Insert
+    long insert(@NonNull Product product);
+
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RawDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RawDao.java
new file mode 100644
index 0000000..e3638e4
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RawDao.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.dao;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.ColumnInfo;
+import androidx.room.Dao;
+import androidx.room.RawQuery;
+import androidx.room.integration.testapp.vo.NameAndLastName;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+import androidx.room.integration.testapp.vo.UserAndPet;
+import androidx.sqlite.db.SupportSQLiteQuery;
+
+import java.util.Date;
+import java.util.List;
+
+@Dao
+public interface RawDao {
+    @RawQuery
+    UserAndAllPets getUserAndAllPets(SupportSQLiteQuery query);
+
+    @RawQuery(observedEntities = UserAndAllPets.class)
+    LiveData<UserAndAllPets> getUserAndAllPetsObservable(SupportSQLiteQuery query);
+
+    @RawQuery
+    User getUser(SupportSQLiteQuery query);
+
+    @RawQuery
+    UserAndPet getUserAndPet(SupportSQLiteQuery query);
+
+    @RawQuery
+    NameAndLastName getUserNameAndLastName(SupportSQLiteQuery query);
+
+    @RawQuery(observedEntities = User.class)
+    NameAndLastName getUserNameAndLastNameWithObserved(SupportSQLiteQuery query);
+
+    @RawQuery
+    int count(SupportSQLiteQuery query);
+
+    @RawQuery
+    List<User> getUserList(SupportSQLiteQuery query);
+
+    @RawQuery
+    List<UserAndPet> getUserAndPetList(SupportSQLiteQuery query);
+
+    @RawQuery(observedEntities = UserAndPet.class)
+    LiveData<List<UserAndPet>> getUserAndPetListObservable(SupportSQLiteQuery query);
+
+    @RawQuery(observedEntities = User.class)
+    LiveData<User> getUserLiveData(SupportSQLiteQuery query);
+
+    @RawQuery
+    UserNameAndBirthday getUserAndBirthday(SupportSQLiteQuery query);
+
+    class UserNameAndBirthday {
+        @ColumnInfo(name = "mName")
+        public final String name;
+        @ColumnInfo(name = "mBirthday")
+        public final Date birthday;
+
+        public UserNameAndBirthday(String name, Date birthday) {
+            this.name = name;
+            this.birthday = birthday;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SchoolDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SchoolDao.java
new file mode 100644
index 0000000..634c451
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SchoolDao.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 androidx.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.RoomWarnings;
+import androidx.room.integration.testapp.vo.Coordinates;
+import androidx.room.integration.testapp.vo.School;
+import androidx.room.integration.testapp.vo.SchoolRef;
+
+import java.util.List;
+
+@Dao
+public abstract class SchoolDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    public abstract void insert(School... schools);
+
+    @Query("SELECT * from School WHERE address_street LIKE '%' || :street || '%'")
+    public abstract List<School> findByStreet(String street);
+
+    @Query("SELECT mId, mName, manager_mName FROM School")
+    public abstract List<School> schoolAndManagerNames();
+
+    @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 mId, address_lat, address_lng FROM School WHERE mId = :schoolId")
+    public abstract School loadCoordinatesAsSchool(int schoolId);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SpecificDogDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SpecificDogDao.java
new file mode 100644
index 0000000..f13e6df
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SpecificDogDao.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 androidx.room.integration.testapp.dao;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Query;
+import androidx.room.integration.testapp.vo.PetsToys;
+
+@Dao
+public interface SpecificDogDao {
+    @Query("SELECT 123 as petId")
+    LiveData<PetsToys> getSpecificDogsToys();
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/ToyDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/ToyDao.java
new file mode 100644
index 0000000..80f8801
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/ToyDao.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 androidx.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.integration.testapp.vo.Toy;
+
+@Dao
+public interface ToyDao {
+    @Insert
+    void insert(Toy... toys);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
new file mode 100644
index 0000000..2fe26db
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserDao.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.dao;
+
+import android.database.Cursor;
+
+import androidx.lifecycle.LiveData;
+import androidx.paging.DataSource;
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.RawQuery;
+import androidx.room.Transaction;
+import androidx.room.Update;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.vo.AvgWeightByAge;
+import androidx.room.integration.testapp.vo.Day;
+import androidx.room.integration.testapp.vo.NameAndLastName;
+import androidx.room.integration.testapp.vo.User;
+import androidx.sqlite.db.SupportSQLiteQuery;
+
+import org.reactivestreams.Publisher;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+
+@SuppressWarnings("SameParameterValue")
+@Dao
+public abstract class UserDao {
+
+    private final TestDatabase mDatabase;
+
+    public UserDao(TestDatabase database) {
+        mDatabase = database;
+    }
+
+    @Query("select * from user where mName like :name")
+    public abstract List<User> findUsersByName(String name);
+
+    @Query("select * from user where mId = :id")
+    public abstract User load(int id);
+
+    @Query("select * from user where mId IN(:ids)")
+    public abstract User[] loadByIds(int... ids);
+
+    @Query("select * from user where custommm = :customField")
+    public abstract List<User> findByCustomField(String customField);
+
+    @Insert
+    public abstract void insert(User user);
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    public abstract void insertOrReplace(User user);
+
+    @Delete
+    public abstract int delete(User user);
+
+    @Delete
+    public abstract int deleteAll(User[] users);
+
+    @Query("delete from user")
+    public abstract int deleteEverything();
+
+    @Update
+    public abstract int update(User user);
+
+    @Update
+    public abstract int updateAll(List<User> users);
+
+    @Insert
+    public abstract void insertAll(User[] users);
+
+    @Query("select * from user where mAdmin = :isAdmin")
+    public abstract List<User> findByAdmin(boolean isAdmin);
+
+    @Query("delete from user where mAge > :age")
+    public abstract int deleteAgeGreaterThan(int age);
+
+    @Query("delete from user where mId IN(:uids)")
+    public abstract int deleteByUids(int... uids);
+
+    @Query("delete from user where mAge >= :min AND mAge <= :max")
+    public abstract int deleteByAgeRange(int min, int max);
+
+    @Query("update user set mName = :name where mId = :id")
+    public abstract int updateById(int id, String name);
+
+    @Query("update user set mId = mId + :amount")
+    public abstract void incrementIds(int amount);
+
+    @Query("update user set mAge = mAge + 1")
+    public abstract void incrementAgeOfAll();
+
+    @Query("select mId from user order by mId ASC")
+    public abstract List<Integer> loadIds();
+
+    @Query("select * from user where mId = :id")
+    public abstract LiveData<User> liveUserById(int id);
+
+    @Transaction
+    @Query("select * from user where mId = :id")
+    public abstract LiveData<User> liveUserByIdInTransaction(int id);
+
+    @Query("select * from user where mName LIKE '%' || :name || '%' ORDER BY mId DESC")
+    public abstract LiveData<List<User>> liveUsersListByName(String name);
+
+    @Query("select * from user where length(mName) = :length")
+    public abstract List<User> findByNameLength(int length);
+
+    @Query("select * from user where mAge = :age")
+    public abstract List<User> findByAge(int age);
+
+    @Query("select mAge, AVG(mWeight) from user GROUP BY mAge ORDER BY 2 DESC")
+    public abstract List<AvgWeightByAge> weightByAge();
+
+    @Query("select mAge, AVG(mWeight) from user GROUP BY mAge ORDER BY 2 DESC LIMIT 1")
+    public abstract LiveData<AvgWeightByAge> maxWeightByAgeGroup();
+
+    @Query("select * from user where mBirthday > :from AND mBirthday < :to")
+    public abstract List<User> findByBirthdayRange(Date from, Date to);
+
+    @Query("select mId from user where mId IN (:ids)")
+    public abstract Cursor findUsersAsCursor(int... ids);
+
+    @Query("select * from user where mId = :id")
+    public abstract Flowable<User> flowableUserById(int id);
+
+    @Query("select * from user where mId = :id")
+    public abstract Maybe<User> maybeUserById(int id);
+
+    @Query("select * from user where mId IN (:ids)")
+    public abstract Maybe<List<User>> maybeUsersByIds(int... ids);
+
+    @Query("select * from user where mId = :id")
+    public abstract Single<User> singleUserById(int id);
+
+    @Query("select * from user where mId IN (:ids)")
+    public abstract Single<List<User>> singleUsersByIds(int... ids);
+
+    @Query("select COUNT(*) from user")
+    public abstract Flowable<Integer> flowableCountUsers();
+
+    @Query("select COUNT(*) from user")
+    public abstract Publisher<Integer> publisherCountUsers();
+
+    @Query("SELECT mBirthday from User where mId = :id")
+    public abstract Date getBirthday(int id);
+
+    @Query("SELECT COUNT(*) from user")
+    public abstract int count();
+
+    @Query("SELECT mAdmin from User where mId = :uid")
+    public abstract boolean isAdmin(int uid);
+
+    @Query("SELECT mAdmin from User where mId = :uid")
+    public abstract LiveData<Boolean> isAdminLiveData(int uid);
+
+    public void insertBothByRunnable(final User a, final User b) {
+        mDatabase.runInTransaction(new Runnable() {
+            @Override
+            public void run() {
+                insert(a);
+                insert(b);
+            }
+        });
+    }
+
+    public int insertBothByCallable(final User a, final User b) {
+        return mDatabase.runInTransaction(new Callable<Integer>() {
+            @Override
+            public Integer call() throws Exception {
+                insert(a);
+                insert(b);
+                return 2;
+            }
+        });
+    }
+
+    @Query("SELECT * FROM user where mAge > :age")
+    public abstract DataSource.Factory<Integer, User> loadPagedByAge(int age);
+
+    @RawQuery(observedEntities = User.class)
+    public abstract DataSource.Factory<Integer, User> loadPagedByAgeWithObserver(
+            SupportSQLiteQuery query);
+
+    // TODO: switch to PositionalDataSource once Room supports it
+    @Query("SELECT * FROM user ORDER BY mAge DESC")
+    public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
+
+    @Query("DELETE FROM User WHERE mId IN (:ids) AND mAge == :age")
+    public abstract int deleteByAgeAndIds(int age, List<Integer> ids);
+
+    @Query("UPDATE User set mWeight = :weight WHERE mId IN (:ids) AND mAge == :age")
+    public abstract int updateByAgeAndIds(float weight, int age, List<Integer> ids);
+
+    @Query("SELECT * FROM user WHERE (mWorkDays & :days) != 0")
+    public abstract List<User> findUsersByWorkDays(Set<Day> days);
+
+    // QueryLoader
+
+    @Query("SELECT COUNT(*) from user")
+    public abstract Integer getUserCount();
+
+    @Query("SELECT u.mName, u.mLastName from user u where mId = :id")
+    public abstract NameAndLastName getNameAndLastName(int id);
+
+    @Transaction
+    public void insertBothByAnnotation(final User a, final User b) {
+        insert(a);
+        insert(b);
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserPetDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserPetDao.java
new file mode 100644
index 0000000..7fd47ef
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/UserPetDao.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 androidx.room.integration.testapp.dao;
+
+import androidx.lifecycle.LiveData;
+import androidx.paging.DataSource;
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.Update;
+import androidx.room.integration.testapp.vo.EmbeddedUserAndAllPets;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+import androidx.room.integration.testapp.vo.UserAndPet;
+import androidx.room.integration.testapp.vo.UserAndPetAdoptionDates;
+import androidx.room.integration.testapp.vo.UserAndPetNonNull;
+import androidx.room.integration.testapp.vo.UserIdAndPetNames;
+import androidx.room.integration.testapp.vo.UserWithPetsAndToys;
+
+import java.util.List;
+
+import io.reactivex.Flowable;
+
+@Dao
+public interface UserPetDao {
+    @Query("SELECT * FROM User u, Pet p WHERE u.mId = p.mUserId")
+    List<UserAndPet> loadAll();
+
+    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
+    List<UserAndPet> loadUsers();
+
+    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
+    List<UserAndPetNonNull> loadUsersWithNonNullPet();
+
+    @Query("SELECT * FROM Pet p LEFT OUTER JOIN User u ON u.mId = p.mUserId")
+    List<UserAndPet> loadPets();
+
+    @Query("SELECT * FROM User u LEFT OUTER JOIN Pet p ON u.mId = p.mUserId")
+    DataSource.Factory<Integer, UserAndAllPets> dataSourceFactoryMultiTable();
+
+    @Query("SELECT * FROM User u")
+    List<UserAndAllPets> loadAllUsersWithTheirPets();
+
+    @Query("SELECT * FROM User u")
+    List<UserIdAndPetNames> loadUserAndPetNames();
+
+    @Query("SELECT * FROM User u")
+    List<UserWithPetsAndToys> loadUserWithPetsAndToys();
+
+    @Query("SELECT * FROM User UNION ALL SELECT * FROM USER")
+    List<UserAndAllPets> unionByItself();
+
+    @Query("SELECT * FROM User")
+    List<UserAndPetAdoptionDates> loadUserWithPetAdoptionDates();
+
+    @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);
+
+    @Insert
+    void insertUserAndPet(User user, Pet pet);
+
+    @Update
+    void updateUsersAndPets(User[] users, Pet[] pets);
+
+    @Delete
+    void delete2UsersAndPets(User user1, User user2, Pet[] pets);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/WithClauseDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/WithClauseDao.java
new file mode 100644
index 0000000..615df10
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/WithClauseDao.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.dao;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Query;
+
+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"
+            + "   UNION ALL \n"
+            + " SELECT n+1, (n+1)*fact FROM factorial \n"
+            + "   WHERE n < :maxIndexInclusive)\n"
+            + " SELECT fact FROM factorial;")
+    List<Integer> getFactorials(int maxIndexInclusive);
+
+    @Query("WITH RECURSIVE factorial(n, fact) AS \n"
+            + "(SELECT 0, 1 \n"
+            + "   UNION ALL \n"
+            + " SELECT n+1, (n+1)*fact FROM factorial \n"
+            + "   WHERE n < :maxIndexInclusive)\n"
+            + " SELECT mName FROM User WHERE User.mId IN (Select fact FROM factorial);")
+    List<String> getUsersWithFactorialIds(int maxIndexInclusive);
+
+    @Query("WITH RECURSIVE factorial(n, fact) AS \n"
+            + "(SELECT 0, 1 \n"
+            + "   UNION ALL \n"
+            + " SELECT n+1, (n+1)*fact FROM factorial \n"
+            + "   WHERE n < :maxIndexInclusive)\n"
+            + " SELECT mName FROM User WHERE User.mId IN (Select fact FROM factorial);")
+    LiveData<List<String>> getUsersWithFactorialIdsLiveData(int maxIndexInclusive);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationDb.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationDb.java
new file mode 100644
index 0000000..f1e2cfa
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationDb.java
@@ -0,0 +1,142 @@
+/*
+ * 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 androidx.room.integration.testapp.migration;
+
+import android.content.ContentValues;
+import android.database.sqlite.SQLiteDatabase;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.RoomDatabase;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import java.util.List;
+
+@SuppressWarnings("WeakerAccess")
+@Database(version = MigrationDb.LATEST_VERSION,
+        entities = {MigrationDb.Entity1.class, MigrationDb.Entity2.class,
+                MigrationDb.Entity4.class})
+public abstract class MigrationDb extends RoomDatabase {
+    static final int LATEST_VERSION = 7;
+    abstract MigrationDao dao();
+    @Entity(indices = {@Index(value = "name", unique = true)})
+    static class Entity1 {
+        public static final String TABLE_NAME = "Entity1";
+        @PrimaryKey
+        public int id;
+        public String name;
+    }
+
+    @Entity
+    static class Entity2 {
+        public static final String TABLE_NAME = "Entity2";
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String addedInV3;
+        public String name;
+    }
+
+    @Entity
+    static class Entity3 { // added in version 4, removed at 6
+        public static final String TABLE_NAME = "Entity3";
+        @PrimaryKey
+        public int id;
+        @Ignore //removed at 5
+        public String removedInV5;
+        public String name;
+    }
+
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = Entity1.class,
+            parentColumns = "name",
+            childColumns = "name",
+            deferred = true)})
+    static class Entity4 {
+        public static final String TABLE_NAME = "Entity4";
+        @PrimaryKey
+        public int id;
+        @ColumnInfo(collate = ColumnInfo.NOCASE)
+        public String name;
+    }
+
+    @Dao
+    interface MigrationDao {
+        @Query("SELECT * from Entity1 ORDER BY id ASC")
+        List<Entity1> loadAllEntity1s();
+        @Query("SELECT * from Entity2 ORDER BY id ASC")
+        List<Entity2> loadAllEntity2s();
+        @Query("SELECT * from Entity2 ORDER BY id ASC")
+        List<Entity2Pojo> loadAllEntity2sAsPojo();
+        @Insert
+        void insert(Entity2... entity2);
+    }
+
+    static class Entity2Pojo extends Entity2 {
+    }
+
+    /**
+     * not a real dao because database will change.
+     */
+    static class Dao_V1 {
+        final SupportSQLiteDatabase mDb;
+
+        Dao_V1(SupportSQLiteDatabase db) {
+            mDb = db;
+        }
+
+        public void insertIntoEntity1(int id, String name) {
+            ContentValues values = new ContentValues();
+            values.put("id", id);
+            values.put("name", name);
+            long insertionId = mDb.insert(Entity1.TABLE_NAME,
+                    SQLiteDatabase.CONFLICT_REPLACE, values);
+            if (insertionId == -1) {
+                throw new RuntimeException("test sanity failure");
+            }
+        }
+    }
+
+    /**
+     * not a real dao because database will change.
+     */
+    static class Dao_V2 {
+        final SupportSQLiteDatabase mDb;
+
+        Dao_V2(SupportSQLiteDatabase db) {
+            mDb = db;
+        }
+
+        public void insertIntoEntity2(int id, String name) {
+            ContentValues values = new ContentValues();
+            values.put("id", id);
+            values.put("name", name);
+            long insertionId = mDb.insert(Entity2.TABLE_NAME,
+                    SQLiteDatabase.CONFLICT_REPLACE, values);
+            if (insertionId == -1) {
+                throw new RuntimeException("test sanity failure");
+            }
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
new file mode 100644
index 0000000..ce0e32a
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
@@ -0,0 +1,434 @@
+/*
+ * 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 androidx.room.integration.testapp.migration;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Room;
+import androidx.room.migration.Migration;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.room.util.TableInfo;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+
+import org.hamcrest.MatcherAssert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Test custom database migrations.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MigrationTest {
+    private static final String TEST_DB = "migration-test";
+    @Rule
+    public MigrationTestHelper helper;
+
+    public MigrationTest() {
+        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
+                MigrationDb.class.getCanonicalName());
+    }
+
+    @Test
+    public void giveBadResource() throws IOException {
+        MigrationTestHelper helper = new MigrationTestHelper(
+                InstrumentationRegistry.getInstrumentation(),
+                "foo", new FrameworkSQLiteOpenHelperFactory());
+        try {
+            helper.createDatabase(TEST_DB, 1);
+            throw new AssertionError("must have failed with missing file exception");
+        } catch (FileNotFoundException exception) {
+            assertThat(exception.getMessage(), containsString("Cannot find"));
+        }
+    }
+
+    @Test
+    public void startInCurrentVersion() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB,
+                MigrationDb.LATEST_VERSION);
+        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
+        dao.insertIntoEntity1(2, "x");
+        db.close();
+        MigrationDb migrationDb = getLatestDb();
+        List<MigrationDb.Entity1> items = migrationDb.dao().loadAllEntity1s();
+        helper.closeWhenFinished(migrationDb);
+        assertThat(items.size(), is(1));
+    }
+
+    @Test
+    public void addTable() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
+        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(db);
+        dao.insertIntoEntity1(2, "foo");
+        dao.insertIntoEntity1(3, "bar");
+        db.close();
+        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
+        new MigrationDb.Dao_V2(db).insertIntoEntity2(3, "blah");
+        db.close();
+        MigrationDb migrationDb = getLatestDb();
+        List<MigrationDb.Entity1> entity1s = migrationDb.dao().loadAllEntity1s();
+
+        assertThat(entity1s.size(), is(2));
+        MigrationDb.Entity2 entity2 = new MigrationDb.Entity2();
+        entity2.id = 2;
+        entity2.name = "bar";
+        // assert no error happens
+        migrationDb.dao().insert(entity2);
+        List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
+        assertThat(entity2s.size(), is(2));
+    }
+
+    private MigrationDb getLatestDb() {
+        MigrationDb db = Room.databaseBuilder(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                MigrationDb.class, TEST_DB).addMigrations(ALL_MIGRATIONS).build();
+        // trigger open
+        db.beginTransaction();
+        db.endTransaction();
+        helper.closeWhenFinished(db);
+        return db;
+    }
+
+    @Test
+    public void addTableFailure() throws IOException {
+        testFailure(1, 2);
+    }
+
+    @Test
+    public void addColumnFailure() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
+        db.close();
+        IllegalStateException caught = null;
+        try {
+            helper.runMigrationsAndValidate(TEST_DB, 3, true, new EmptyMigration(2, 3));
+        } catch (IllegalStateException ex) {
+            caught = ex;
+        }
+        assertThat(caught, instanceOf(IllegalStateException.class));
+    }
+
+    @Test
+    public void addColumn() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
+        MigrationDb.Dao_V2 v2Dao = new MigrationDb.Dao_V2(db);
+        v2Dao.insertIntoEntity2(7, "blah");
+        db.close();
+        helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3);
+        // trigger open.
+        MigrationDb migrationDb = getLatestDb();
+        List<MigrationDb.Entity2> entity2s = migrationDb.dao().loadAllEntity2s();
+        assertThat(entity2s.size(), is(1));
+        assertThat(entity2s.get(0).name, is("blah"));
+        assertThat(entity2s.get(0).addedInV3, is(nullValue()));
+
+        List<MigrationDb.Entity2Pojo> entity2Pojos = migrationDb.dao().loadAllEntity2sAsPojo();
+        assertThat(entity2Pojos.size(), is(1));
+        assertThat(entity2Pojos.get(0).name, is("blah"));
+        assertThat(entity2Pojos.get(0).addedInV3, is(nullValue()));
+    }
+
+    @Test
+    public void failedToRemoveColumn() throws IOException {
+        testFailure(4, 5);
+    }
+
+    @Test
+    public void removeColumn() throws IOException {
+        helper.createDatabase(TEST_DB, 4);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                5, true, MIGRATION_4_5);
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
+        assertThat(info.columns.size(), is(2));
+    }
+
+    @Test
+    public void dropTable() throws IOException {
+        helper.createDatabase(TEST_DB, 5);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                6, true, MIGRATION_5_6);
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
+        assertThat(info.columns.size(), is(0));
+    }
+
+    @Test
+    public void failedToDropTable() throws IOException {
+        testFailure(5, 6);
+    }
+
+    @Test
+    public void failedToDropTableDontVerify() throws IOException {
+        helper.createDatabase(TEST_DB, 5);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                6, false, new EmptyMigration(5, 6));
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity3.TABLE_NAME);
+        assertThat(info.columns.size(), is(2));
+    }
+
+    @Test
+    public void failedForeignKey() throws IOException {
+        final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 6);
+        db.close();
+        Throwable throwable = null;
+        try {
+            helper.runMigrationsAndValidate(TEST_DB,
+                    7, false, new Migration(6, 7) {
+                        @Override
+                        public void migrate(SupportSQLiteDatabase database) {
+                            database.execSQL("CREATE TABLE Entity4 (`id` INTEGER NOT NULL,"
+                                    + " `name` TEXT, PRIMARY KEY(`id`))");
+                        }
+                    });
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertThat(throwable, instanceOf(IllegalStateException.class));
+        //noinspection ConstantConditions
+        assertThat(throwable.getMessage(), containsString("Migration failed"));
+    }
+
+    @Test
+    public void newTableWithForeignKey() throws IOException {
+        helper.createDatabase(TEST_DB, 6);
+        final SupportSQLiteDatabase db = helper.runMigrationsAndValidate(TEST_DB,
+                7, false, MIGRATION_6_7);
+        final TableInfo info = TableInfo.read(db, MigrationDb.Entity4.TABLE_NAME);
+        assertThat(info.foreignKeys.size(), is(1));
+    }
+
+    @Test
+    public void missingMigration() throws IOException {
+        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
+        database.close();
+        try {
+            Context targetContext = InstrumentationRegistry.getTargetContext();
+            MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
+                    .build();
+            db.dao().loadAllEntity1s();
+            throw new AssertionError("Should've failed :/");
+        } catch (IllegalStateException ignored) {
+        }
+    }
+
+    @Test
+    public void missingMigrationNuke() throws IOException {
+        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 1);
+        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(database);
+        dao.insertIntoEntity1(2, "foo");
+        dao.insertIntoEntity1(3, "bar");
+        database.close();
+
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+        MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
+                .fallbackToDestructiveMigration()
+                .build();
+        assertThat(db.dao().loadAllEntity1s().size(), is(0));
+        db.close();
+    }
+
+    @Test
+    public void failWithIdentityCheck() throws IOException {
+        for (int i = 1; i < MigrationDb.LATEST_VERSION; i++) {
+            String name = "test_" + i;
+            helper.createDatabase(name, i).close();
+            IllegalStateException exception = null;
+            try {
+                MigrationDb db = Room.databaseBuilder(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                        MigrationDb.class, name).build();
+                db.runInTransaction(new Runnable() {
+                    @Override
+                    public void run() {
+                        // do nothing
+                    }
+                });
+            } catch (IllegalStateException ex) {
+                exception = ex;
+            }
+            MatcherAssert.assertThat("identity detection should've failed",
+                    exception, notNullValue());
+        }
+    }
+
+    @Test
+    public void fallbackToDestructiveMigrationFrom_destructiveMigrationOccursForSuppliedVersion()
+            throws IOException {
+        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 6);
+        final MigrationDb.Dao_V1 dao = new MigrationDb.Dao_V1(database);
+        dao.insertIntoEntity1(2, "foo");
+        dao.insertIntoEntity1(3, "bar");
+        database.close();
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+
+        MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
+                .fallbackToDestructiveMigrationFrom(6)
+                .build();
+
+        assertThat(db.dao().loadAllEntity1s().size(), is(0));
+    }
+
+    @Test
+    public void fallbackToDestructiveMigrationFrom_suppliedValueIsMigrationStartVersion_exception()
+            throws IOException {
+        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 6);
+        database.close();
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+
+        Throwable throwable = null;
+        try {
+            Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
+                    .addMigrations(MIGRATION_6_7)
+                    .fallbackToDestructiveMigrationFrom(6)
+                    .build();
+        } catch (Throwable t) {
+            throwable = t;
+        }
+
+        assertThat(throwable, is(not(nullValue())));
+        //noinspection ConstantConditions
+        assertThat(throwable.getMessage(),
+                startsWith("Inconsistency detected. A Migration was supplied to"));
+        assertThat(throwable.getMessage(), endsWith("6"));
+    }
+
+    @Test
+    public void fallbackToDestructiveMigrationFrom_suppliedValueIsMigrationEndVersion_exception()
+            throws IOException {
+        SupportSQLiteDatabase database = helper.createDatabase(TEST_DB, 5);
+        database.close();
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+
+        Throwable throwable = null;
+        try {
+            Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
+                    .addMigrations(MIGRATION_5_6)
+                    .fallbackToDestructiveMigrationFrom(6)
+                    .build();
+        } catch (Throwable t) {
+            throwable = t;
+        }
+
+        assertThat(throwable, is(not(nullValue())));
+        //noinspection ConstantConditions
+        assertThat(throwable.getMessage(),
+                startsWith("Inconsistency detected. A Migration was supplied to"));
+        assertThat(throwable.getMessage(), endsWith("6"));
+    }
+
+    private void testFailure(int startVersion, int endVersion) throws IOException {
+        final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, startVersion);
+        db.close();
+        Throwable throwable = null;
+        try {
+            helper.runMigrationsAndValidate(TEST_DB, endVersion, true,
+                    new EmptyMigration(startVersion, endVersion));
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertThat(throwable, instanceOf(IllegalStateException.class));
+        //noinspection ConstantConditions
+        assertThat(throwable.getMessage(), containsString("Migration failed"));
+    }
+
+    private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity2` ("
+                    + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+                    + " `name` TEXT)");
+        }
+    };
+
+    private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("ALTER TABLE " + MigrationDb.Entity2.TABLE_NAME
+                    + " ADD COLUMN addedInV3 TEXT");
+        }
+    };
+
+    private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3` (`id` INTEGER NOT NULL,"
+                    + " `removedInV5` TEXT, `name` TEXT, PRIMARY KEY(`id`))");
+        }
+    };
+
+    private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS `Entity3_New` (`id` INTEGER NOT NULL,"
+                    + " `name` TEXT, PRIMARY KEY(`id`))");
+            database.execSQL("INSERT INTO Entity3_New(`id`, `name`) "
+                    + "SELECT `id`, `name` FROM Entity3");
+            database.execSQL("DROP TABLE Entity3");
+            database.execSQL("ALTER TABLE Entity3_New RENAME TO Entity3");
+        }
+    };
+
+    private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("DROP TABLE " + MigrationDb.Entity3.TABLE_NAME);
+        }
+    };
+
+    private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            database.execSQL("CREATE TABLE IF NOT EXISTS " + MigrationDb.Entity4.TABLE_NAME
+                    + " (`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`)");
+        }
+    };
+
+    private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_1_2,
+            MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7};
+
+    static final class EmptyMigration extends Migration {
+        EmptyMigration(int startVersion, int endVersion) {
+            super(startVersion, endVersion);
+        }
+
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            // do nothing
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/DataSourceFactoryTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/DataSourceFactoryTest.java
new file mode 100644
index 0000000..92a43b7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/DataSourceFactoryTest.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 androidx.room.integration.testapp.paging;
+
+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 static org.junit.Assert.assertNotNull;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.paging.DataSource;
+import androidx.paging.LivePagedListBuilder;
+import androidx.paging.PagedList;
+import androidx.room.integration.testapp.test.TestDatabaseTest;
+import androidx.room.integration.testapp.test.TestUtil;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+import androidx.sqlite.db.SimpleSQLiteQuery;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class DataSourceFactoryTest extends TestDatabaseTest {
+    @Rule
+    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+
+    private interface LivePagedListFactory {
+        LiveData<PagedList<User>> create();
+    }
+
+    @Test
+    public void getUsersAsPagedList()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        validateUsersAsPagedList(() -> new LivePagedListBuilder<>(
+                mUserDao.loadPagedByAge(3),
+                new PagedList.Config.Builder()
+                        .setPageSize(10)
+                        .setPrefetchDistance(1)
+                        .setInitialLoadSizeHint(10).build())
+                .build());
+    }
+
+    @Test
+    public void getUsersAsPagedList_ViaRawQuery_WithObservable()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        SimpleSQLiteQuery query = new SimpleSQLiteQuery(
+                "SELECT * FROM user where mAge > ?",
+                new Object[]{3});
+        validateUsersAsPagedList(() -> new LivePagedListBuilder<>(
+                mUserDao.loadPagedByAgeWithObserver(query),
+                new PagedList.Config.Builder()
+                        .setPageSize(10)
+                        .setPrefetchDistance(1)
+                        .setInitialLoadSizeHint(10).build())
+                .build());
+    }
+
+    private void validateUsersAsPagedList(
+            LivePagedListFactory factory)
+            throws InterruptedException, ExecutionException, TimeoutException {
+        mDatabase.beginTransaction();
+        try {
+            for (int i = 0; i < 100; i++) {
+                final User user = TestUtil.createUser(i + 1);
+                user.setAge(i);
+                mUserDao.insert(user);
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+        assertThat(mUserDao.count(), is(100));
+
+        final LiveData<PagedList<User>> livePagedUsers = factory.create();
+
+        final TestLifecycleOwner testOwner = new TestLifecycleOwner();
+        testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
+        drain();
+        PagedListObserver<User> observer = new PagedListObserver<>();
+
+        observe(livePagedUsers, testOwner, observer);
+        assertThat(observer.get(), nullValue());
+        observer.reset();
+
+        testOwner.handleEvent(Lifecycle.Event.ON_START);
+        drain();
+
+        PagedList<User> pagedList1 = observer.get();
+        assertThat(pagedList1, is(notNullValue()));
+
+        assertThat(pagedList1.size(), is(96));
+        assertThat(getAndLoad(pagedList1, 20), is(nullValue()));
+        drain();
+        assertThat(getAndLoad(pagedList1, 31), nullValue());
+        assertThat(getAndLoad(pagedList1, 20), notNullValue());
+        assertThat(getAndLoad(pagedList1, 16), notNullValue());
+
+        drain();
+        assertThat(getAndLoad(pagedList1, 31), notNullValue());
+        assertThat(getAndLoad(pagedList1, 50), nullValue());
+        drain();
+        assertThat(getAndLoad(pagedList1, 50), notNullValue());
+        observer.reset();
+        // now invalidate the database but don't get the new paged list
+        mUserDao.updateById(50, "foo");
+        assertThat(getAndLoad(pagedList1, 70), nullValue());
+        drain();
+        assertThat(getAndLoad(pagedList1, 70), nullValue());
+        PagedList<User> pagedList = observer.get();
+        assertThat(getAndLoad(pagedList, 70), notNullValue());
+    }
+
+    private <T> T getAndLoad(PagedList<T> list, int pos) {
+        T result = list.get(pos);
+        list.loadAround(pos);
+        return result;
+    }
+
+    private void drain() throws InterruptedException, TimeoutException {
+        mExecutorRule.drainTasks(60, TimeUnit.SECONDS);
+    }
+
+    private void observe(final LiveData liveData, final LifecycleOwner provider,
+            final Observer observer) throws ExecutionException, InterruptedException {
+        FutureTask<Void> futureTask = new FutureTask<>(() -> {
+            //noinspection unchecked
+            liveData.observe(provider, observer);
+            return null;
+        });
+        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
+        futureTask.get();
+    }
+
+    @Test
+    public void withRelation() throws ExecutionException, InterruptedException, TimeoutException {
+        // verify DataSourceFactory can be created from a multi table join
+        DataSource.Factory<Integer, UserAndAllPets> factory =
+                mUserPetDao.dataSourceFactoryMultiTable();
+        LiveData<PagedList<UserAndAllPets>> liveData =
+                new LivePagedListBuilder<>(mUserPetDao.dataSourceFactoryMultiTable(), 10).build();
+        assertNotNull(factory.create());
+
+        PagedListObserver<UserAndAllPets> observer = new PagedListObserver<>();
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        observe(liveData, lifecycleOwner, observer);
+        drain();
+        assertThat(observer.get(), is(Collections.emptyList()));
+
+        observer.reset();
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        final UserAndAllPets noPets = observer.get().get(0);
+        assertThat(noPets.user, is(user));
+
+        observer.reset();
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mPetDao.insertAll(pets);
+
+        drain();
+        final UserAndAllPets withPets = observer.get().get(0);
+        assertThat(withPets.user, is(user));
+        assertThat(withPets.pets, is(Arrays.asList(pets)));
+    }
+
+    static class TestLifecycleOwner implements LifecycleOwner {
+
+        private LifecycleRegistry mLifecycle;
+
+        TestLifecycleOwner() {
+            mLifecycle = new LifecycleRegistry(this);
+        }
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return mLifecycle;
+        }
+
+        void handleEvent(Lifecycle.Event event) {
+            mLifecycle.handleLifecycleEvent(event);
+        }
+    }
+
+    private static class PagedListObserver<T> implements Observer<PagedList<T>> {
+        private PagedList<T> mList;
+
+        void reset() {
+            mList = null;
+        }
+
+        public PagedList<T> get() {
+            return mList;
+        }
+
+        @Override
+        public void onChanged(@Nullable PagedList<T> pagedList) {
+            mList = pagedList;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/LimitOffsetDataSourceTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
new file mode 100644
index 0000000..c4c6c16
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/paging/LimitOffsetDataSourceTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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 androidx.room.integration.testapp.paging;
+
+import static junit.framework.Assert.assertFalse;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+import androidx.room.integration.testapp.test.TestDatabaseTest;
+import androidx.room.integration.testapp.test.TestUtil;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.paging.LimitOffsetDataSource;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LimitOffsetDataSourceTest extends TestDatabaseTest {
+
+    @After
+    public void teardown() {
+        mUserDao.deleteEverything();
+    }
+
+    private LimitOffsetDataSource<User> loadUsersByAgeDesc() {
+        return (LimitOffsetDataSource<User>) mUserDao.loadUsersByAgeDesc().create();
+    }
+
+    @Test
+    public void emptyPage() {
+        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
+        assertThat(dataSource.countItems(), is(0));
+    }
+
+    @Test
+    public void loadCount() {
+        createUsers(6);
+        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
+        assertThat(dataSource.countItems(), is(6));
+    }
+
+    @Test
+    public void singleItem() {
+        List<User> users = createUsers(1);
+        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
+        assertThat(dataSource.countItems(), is(1));
+        List<User> initial = dataSource.loadRange(0, 10);
+
+        assertThat(initial.get(0), is(users.get(0)));
+        assertFalse(dataSource.loadRange(1, 10).iterator().hasNext());
+    }
+
+    @Test
+    public void initial() {
+        List<User> users = createUsers(10);
+        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
+        assertThat(dataSource.countItems(), is(10));
+        List<User> initial = dataSource.loadRange(0, 1);
+        assertThat(initial.get(0), is(users.get(0)));
+        List<User> second = dataSource.loadRange(1, 1);
+        assertThat(second.get(0), is(users.get(1)));
+    }
+
+    @Test
+    public void loadAll() {
+        List<User> users = createUsers(10);
+
+        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
+        List<User> all = dataSource.loadRange(0, 10);
+        assertThat(users, is(all));
+    }
+
+    @Test
+    public void loadAfter() {
+        List<User> users = createUsers(10);
+        LimitOffsetDataSource<User> dataSource = loadUsersByAgeDesc();
+        List<User> result = dataSource.loadRange(4, 2);
+        assertThat(result, is(users.subList(4, 6)));
+    }
+
+    @NonNull
+    private List<User> createUsers(int count) {
+        List<User> users = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            User user = TestUtil.createUser(i);
+            user.setAge(1);
+            mUserDao.insert(user);
+            users.add(user);
+        }
+        return users;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ClearAllTablesTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ClearAllTablesTest.java
new file mode 100644
index 0000000..8bffdef
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ClearAllTablesTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.junit.Assert.assertThat;
+
+import android.content.Context;
+import android.database.Cursor;
+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;
+
+import androidx.annotation.NonNull;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Insert;
+import androidx.room.InvalidationTracker;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SuppressWarnings("WeakerAccess")
+@RunWith(AndroidJUnit4.class)
+public class ClearAllTablesTest {
+
+    @Database(version = 1, entities = {Parent.class, Child.class}, exportSchema = false)
+    public abstract static class ClearAllTablesDatabase extends RoomDatabase {
+        abstract ClearAllTablesDao dao();
+    }
+
+    @Entity
+    public static class Parent {
+        @PrimaryKey
+        public long id;
+        public String name;
+
+        public Parent(long id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+    }
+
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = Parent.class, parentColumns = "id", childColumns = "parentId")})
+    public static class Child {
+        @PrimaryKey
+        public long id;
+        public String name;
+        public long parentId;
+
+        public Child(long id, String name, long parentId) {
+            this.id = id;
+            this.name = name;
+            this.parentId = parentId;
+        }
+    }
+
+    @Dao
+    public interface ClearAllTablesDao {
+        @Insert
+        void insertParent(Parent parent);
+
+        @Insert
+        void insertChild(Child child);
+
+        @Query("SELECT COUNT(*) FROM Parent")
+        int countParent();
+
+        @Query("SELECT COUNT(*) FROM Child")
+        int countChild();
+    }
+
+    private ClearAllTablesDatabase mDatabase;
+    private ClearAllTablesDao mDao;
+
+    @Before
+    public void setUp() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        mDatabase = Room.inMemoryDatabaseBuilder(context, ClearAllTablesDatabase.class).build();
+        mDao = mDatabase.dao();
+    }
+
+    @After
+    public void closeDatabase() {
+        mDatabase.close();
+    }
+
+    @Test
+    @SmallTest
+    public void simple() {
+        mDao.insertParent(new Parent(1, "A"));
+        assertThat(mDao.countParent(), is(1));
+        mDatabase.clearAllTables();
+        assertThat(mDao.countParent(), is(0));
+    }
+
+    @Test
+    @SmallTest
+    public void foreignKey() {
+        mDao.insertParent(new Parent(1, "A"));
+        mDao.insertChild(new Child(1, "a", 1));
+        assertThat(mDao.countParent(), is(1));
+        assertThat(mDao.countChild(), is(1));
+        mDatabase.clearAllTables();
+        assertThat(mDao.countParent(), is(0));
+        assertThat(mDao.countChild(), is(0));
+    }
+
+    @Test
+    @SmallTest
+    public void observer() throws InterruptedException {
+        mDao.insertParent(new Parent(1, "A"));
+        assertThat(mDao.countParent(), is(1));
+        final CountDownLatch latch = new CountDownLatch(1);
+        mDatabase.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("Parent") {
+            @Override
+            public void onInvalidated(@NonNull Set<String> tables) {
+                assertThat(tables, hasSize(1));
+                assertThat(tables, hasItem("Parent"));
+                assertThat(mDao.countParent(), is(0));
+                latch.countDown();
+            }
+        });
+        mDatabase.clearAllTables();
+        assertThat(latch.await(3000, TimeUnit.MILLISECONDS), is(true));
+    }
+
+    @Test
+    @MediumTest
+    public void clearsDataFromDiskTruncate() throws IOException {
+        clearsDataFromDisk(RoomDatabase.JournalMode.TRUNCATE);
+    }
+
+    @Test
+    @MediumTest
+    @SdkSuppress(minSdkVersion = 16)
+    public void clearsDataFromDiskWal() throws IOException {
+        clearsDataFromDisk(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING);
+    }
+
+    private void clearsDataFromDisk(RoomDatabase.JournalMode journalMode) throws IOException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String dbName = "clear.db";
+        context.deleteDatabase(dbName);
+        ClearAllTablesDatabase db;
+        db = Room.databaseBuilder(context, ClearAllTablesDatabase.class, dbName)
+                .setJournalMode(journalMode).build();
+        final File file = context.getDatabasePath(dbName);
+        final String uuid = UUID.randomUUID().toString();
+        db.dao().insertParent(new Parent(1, uuid));
+        assertThat(queryEncoding(db), is(equalTo("UTF-8")));
+        db.close();
+        assertThat(containsString(file, uuid), is(true));
+        db = Room.databaseBuilder(context, ClearAllTablesDatabase.class, dbName)
+                .setJournalMode(journalMode).build();
+        db.clearAllTables();
+        db.close();
+        assertThat(containsString(file, uuid), is(false));
+    }
+
+    private String queryEncoding(RoomDatabase db) {
+        Cursor c = null;
+        try {
+            c = db.query("PRAGMA encoding", null);
+            c.moveToFirst();
+            return c.getString(0);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private boolean containsString(File file, String s) throws IOException {
+        final byte[] content = new byte[(int) file.length()];
+        final FileInputStream stream = new FileInputStream(file);
+        //noinspection TryFinallyCanBeTryWithResources
+        try {
+            assertThat(stream.read(content), is(content.length));
+            return new String(content, Charset.forName("UTF-8")).indexOf(s) > 0;
+        } finally {
+            stream.close();
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/CollationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/CollationTest.java
new file mode 100644
index 0000000..ca0d2e0
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/CollationTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CollationTest {
+    private CollateDb mDb;
+    private CollateDao mDao;
+    private Locale mDefaultLocale;
+    private final CollateEntity mItem1 = new CollateEntity(1, "abı");
+    private final CollateEntity mItem2 = new CollateEntity(2, "abi");
+    private final CollateEntity mItem3 = new CollateEntity(3, "abj");
+    private final CollateEntity mItem4 = new CollateEntity(4, "abç");
+
+    @Before
+    public void init() {
+        mDefaultLocale = Locale.getDefault();
+    }
+
+    private void initDao(Locale systemLocale) {
+        Locale.setDefault(systemLocale);
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                CollateDb.class).build();
+        mDao = mDb.dao();
+        mDao.insert(mItem1);
+        mDao.insert(mItem2);
+        mDao.insert(mItem3);
+        mDao.insert(mItem4);
+    }
+
+    @After
+    public void closeDb() {
+        mDb.close();
+        Locale.setDefault(mDefaultLocale);
+    }
+
+    @Test
+    public void localized() {
+        initDao(new Locale("tr", "TR"));
+        List<CollateEntity> result = mDao.sortedByLocalized();
+        assertThat(result, CoreMatchers.is(Arrays.asList(
+                mItem4, mItem1, mItem2, mItem3
+        )));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void localized_asUnicode() {
+        initDao(Locale.getDefault());
+        List<CollateEntity> result = mDao.sortedByLocalizedAsUnicode();
+        assertThat(result, CoreMatchers.is(Arrays.asList(
+                mItem4, mItem2, mItem1, mItem3
+        )));
+    }
+
+    @Test
+    public void unicode_asLocalized() {
+        initDao(new Locale("tr", "TR"));
+        List<CollateEntity> result = mDao.sortedByUnicodeAsLocalized();
+        assertThat(result, CoreMatchers.is(Arrays.asList(
+                mItem4, mItem1, mItem2, mItem3
+        )));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void unicode() {
+        initDao(Locale.getDefault());
+        List<CollateEntity> result = mDao.sortedByUnicode();
+        assertThat(result, CoreMatchers.is(Arrays.asList(
+                mItem4, mItem2, mItem1, mItem3
+        )));
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @androidx.room.Entity
+    static class CollateEntity {
+        @PrimaryKey
+        public final int id;
+        @ColumnInfo(collate = ColumnInfo.LOCALIZED)
+        public final String localizedName;
+        @ColumnInfo(collate = ColumnInfo.UNICODE)
+        public final String unicodeName;
+
+        CollateEntity(int id, String name) {
+            this.id = id;
+            this.localizedName = name;
+            this.unicodeName = name;
+        }
+
+        CollateEntity(int id, String localizedName, String unicodeName) {
+            this.id = id;
+            this.localizedName = localizedName;
+            this.unicodeName = unicodeName;
+        }
+
+        @SuppressWarnings("SimplifiableIfStatement")
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            CollateEntity that = (CollateEntity) o;
+
+            if (id != that.id) return false;
+            if (!localizedName.equals(that.localizedName)) return false;
+            return unicodeName.equals(that.unicodeName);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = id;
+            result = 31 * result + localizedName.hashCode();
+            result = 31 * result + unicodeName.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "CollateEntity{"
+                    + "id=" + id
+                    + ", localizedName='" + localizedName + '\''
+                    + ", unicodeName='" + unicodeName + '\''
+                    + '}';
+        }
+    }
+
+    @Dao
+    interface CollateDao {
+        @Query("SELECT * FROM CollateEntity ORDER BY localizedName ASC")
+        List<CollateEntity> sortedByLocalized();
+
+        @Query("SELECT * FROM CollateEntity ORDER BY localizedName COLLATE UNICODE ASC")
+        List<CollateEntity> sortedByLocalizedAsUnicode();
+
+        @Query("SELECT * FROM CollateEntity ORDER BY unicodeName ASC")
+        List<CollateEntity> sortedByUnicode();
+
+        @Query("SELECT * FROM CollateEntity ORDER BY unicodeName COLLATE LOCALIZED ASC")
+        List<CollateEntity> sortedByUnicodeAsLocalized();
+
+        @Insert
+        void insert(CollateEntity... entities);
+    }
+
+    @Database(entities = CollateEntity.class, version = 1, exportSchema = false)
+    abstract static class CollateDb extends RoomDatabase {
+        abstract CollateDao dao();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ConstructorTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ConstructorTest.java
new file mode 100644
index 0000000..53475b3
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ConstructorTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Embedded;
+import androidx.room.Entity;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("SqlNoDataSourceInspection")
+@SmallTest
+public class ConstructorTest {
+    @Database(version = 1, entities = {FullConstructor.class, PartialConstructor.class,
+            EntityWithAnnotations.class},
+            exportSchema = false)
+    abstract static class MyDb extends RoomDatabase {
+        abstract MyDao dao();
+    }
+
+    @Dao
+    interface MyDao {
+        @Insert
+        void insertFull(FullConstructor... full);
+
+        @Query("SELECT * FROM fc WHERE a = :a")
+        FullConstructor loadFull(int a);
+
+        @Insert
+        void insertPartial(PartialConstructor... partial);
+
+        @Query("SELECT * FROM pc WHERE a = :a")
+        PartialConstructor loadPartial(int a);
+
+        @Insert
+        void insertEntityWithAnnotations(EntityWithAnnotations... items);
+
+        @Query("SELECT * FROM EntityWithAnnotations")
+        EntityWithAnnotations getEntitiWithAnnotations();
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(tableName = "fc")
+    static class FullConstructor {
+        @PrimaryKey
+        public final int a;
+        public final int b;
+        @Embedded
+        public final MyEmbedded embedded;
+
+        FullConstructor(int a, int b, MyEmbedded embedded) {
+            this.a = a;
+            this.b = b;
+            this.embedded = embedded;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            FullConstructor that = (FullConstructor) o;
+
+            if (a != that.a) return false;
+            //noinspection SimplifiableIfStatement
+            if (b != that.b) return false;
+            return embedded != null ? embedded.equals(that.embedded)
+                    : that.embedded == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = a;
+            result = 31 * result + b;
+            result = 31 * result + (embedded != null ? embedded.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(tableName = "pc")
+    static class PartialConstructor {
+        @PrimaryKey
+        public final int a;
+        public int b;
+        @Embedded
+        private MyEmbedded mEmbedded;
+
+        PartialConstructor(int a) {
+            this.a = a;
+        }
+
+        public MyEmbedded getEmbedded() {
+            return mEmbedded;
+        }
+
+        public void setEmbedded(MyEmbedded embedded) {
+            mEmbedded = embedded;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            PartialConstructor that = (PartialConstructor) o;
+
+            if (a != that.a) return false;
+            //noinspection SimplifiableIfStatement
+            if (b != that.b) return false;
+            return mEmbedded != null ? mEmbedded.equals(that.mEmbedded)
+                    : that.mEmbedded == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = a;
+            result = 31 * result + b;
+            result = 31 * result + (mEmbedded != null ? mEmbedded.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class MyEmbedded {
+        public final String text;
+
+        MyEmbedded(String text) {
+            this.text = text;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            MyEmbedded that = (MyEmbedded) o;
+
+            return text != null ? text.equals(that.text) : that.text == null;
+        }
+
+        @Override
+        public int hashCode() {
+            return text != null ? text.hashCode() : 0;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity
+    static class EntityWithAnnotations {
+        @PrimaryKey
+        @NonNull
+        public final String id;
+
+        @NonNull
+        public final String username;
+
+        @Nullable
+        public final String displayName;
+
+        EntityWithAnnotations(
+                @NonNull String id,
+                @NonNull String username,
+                @Nullable String displayName) {
+            this.id = id;
+            this.username = username;
+            this.displayName = displayName;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            EntityWithAnnotations that = (EntityWithAnnotations) o;
+
+            if (!id.equals(that.id)) return false;
+            if (!username.equals(that.username)) return false;
+            return displayName != null ? displayName.equals(that.displayName)
+                    : that.displayName == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = id.hashCode();
+            result = 31 * result + username.hashCode();
+            result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
+            return result;
+        }
+    }
+
+    private MyDb mDb;
+    private MyDao mDao;
+
+    @Before
+    public void init() {
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(), MyDb.class)
+                .build();
+        mDao = mDb.dao();
+    }
+
+    @Test
+    public void insertAndReadFullConstructor() {
+        FullConstructor inserted = new FullConstructor(1, 2, null);
+        mDao.insertFull(inserted);
+        final FullConstructor load = mDao.loadFull(1);
+        assertThat(load, is(inserted));
+    }
+
+    @Test
+    public void insertAndReadPartial() {
+        PartialConstructor item = new PartialConstructor(3);
+        item.b = 7;
+        mDao.insertPartial(item);
+        PartialConstructor load = mDao.loadPartial(3);
+        assertThat(load, is(item));
+    }
+
+    @Test // for bug b/69562125
+    public void entityWithAnnotations() {
+        EntityWithAnnotations item = new EntityWithAnnotations("a", "b", null);
+        mDao.insertEntityWithAnnotations(item);
+        assertThat(mDao.getEntitiWithAnnotations(), is(item));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/CustomDatabaseTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/CustomDatabaseTest.java
new file mode 100644
index 0000000..efe7bc1
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/CustomDatabaseTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.integration.testapp.database.Customer;
+import androidx.room.integration.testapp.database.SampleDatabase;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.SupportSQLiteQuery;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class CustomDatabaseTest {
+
+    @Test
+    public void invalidationTrackerAfterClose() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        RoomDatabase.Builder<SampleDatabase> builder =
+                Room.databaseBuilder(context, SampleDatabase.class, "db")
+                        .openHelperFactory(new RethrowExceptionFactory());
+        Customer customer = new Customer();
+        for (int i = 0; i < 100; i++) {
+            SampleDatabase db = builder.build();
+            db.getCustomerDao().insert(customer);
+            // Give InvalidationTracker enough time to start #mRefreshRunnable and pass the
+            // initialization check.
+            SystemClock.sleep(1);
+            // InvalidationTracker#mRefreshRunnable will cause race condition if its database query
+            // happens after close.
+            db.close();
+        }
+    }
+
+    /**
+     * This is mostly {@link FrameworkSQLiteOpenHelperFactory}, but the returned {@link
+     * SupportSQLiteDatabase} fails with {@link RuntimeException} instead of {@link
+     * IllegalStateException} or {@link SQLiteException}. This way, we can simulate custom database
+     * implementation that throws its own exception types.
+     */
+    private static class RethrowExceptionFactory implements SupportSQLiteOpenHelper.Factory {
+
+        @Override
+        public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
+            final FrameworkSQLiteOpenHelperFactory factory = new FrameworkSQLiteOpenHelperFactory();
+            final SupportSQLiteOpenHelper helper = factory.create(configuration);
+            SupportSQLiteOpenHelper helperMock = mock(SupportSQLiteOpenHelper.class,
+                    delegatesTo(helper));
+            // Inject mocks to the object hierarchy.
+            doAnswer(new Answer() {
+                @Override
+                public SupportSQLiteDatabase answer(InvocationOnMock invocation)
+                        throws Throwable {
+                    final SupportSQLiteDatabase db = helper.getWritableDatabase();
+                    SupportSQLiteDatabase dbMock = mock(SupportSQLiteDatabase.class,
+                            delegatesTo(db));
+                    doAnswer(new Answer() {
+                        @Override
+                        public Cursor answer(InvocationOnMock invocation) throws Throwable {
+                            SupportSQLiteQuery query = invocation.getArgument(0);
+                            try {
+                                return db.query(query);
+                            } catch (IllegalStateException | SQLiteException e) {
+                                // Rethrow the exception in order to simulate the way custom
+                                // database implementation throws its own exception types.
+                                throw new RuntimeException("closed", e);
+                            }
+                        }
+                    }).when(dbMock).query(any(SupportSQLiteQuery.class));
+                    return dbMock;
+                }
+            }).when(helperMock).getWritableDatabase();
+            return helperMock;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/DaoNameConflictTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/DaoNameConflictTest.java
new file mode 100644
index 0000000..d2a243e
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/DaoNameConflictTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DaoNameConflictTest {
+    private ConflictDatabase mDb;
+    @Before
+    public void init() {
+        mDb = Room.inMemoryDatabaseBuilder(
+                InstrumentationRegistry.getTargetContext(),
+                ConflictDatabase.class
+        ).build();
+    }
+
+    @After
+    public void close() {
+        mDb.close();
+    }
+
+    @Test
+    public void readFromItem1() {
+        Item1 item1 = new Item1(1, "a");
+        mDb.item1Dao().insert(item1);
+        Item2 item2 = new Item2(2, "b");
+        mDb.item2Dao().insert(item2);
+        assertThat(mDb.item1Dao().get(), is(item1));
+        assertThat(mDb.item2Dao().get(), is(item2));
+    }
+
+    @Entity
+    static class Item1 {
+        @PrimaryKey
+        public int id;
+        public String name;
+
+        Item1(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        @Dao
+        public interface Store {
+            @Query("SELECT * FROM Item1 LIMIT 1")
+            Item1 get();
+            @Insert
+            void insert(Item1... items);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Item1 item1 = (Item1) o;
+
+            //noinspection SimplifiableIfStatement
+            if (id != item1.id) return false;
+            return name != null ? name.equals(item1.name) : item1.name == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = id;
+            result = 31 * result + (name != null ? name.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Entity
+    static class Item2 {
+        @PrimaryKey
+        public int id;
+        public String name;
+
+        Item2(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        @Dao
+        public interface Store {
+            @Query("SELECT * FROM Item2 LIMIT 1")
+            Item2 get();
+            @Insert
+            void insert(Item2... items);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Item2 item2 = (Item2) o;
+
+            //noinspection SimplifiableIfStatement
+            if (id != item2.id) return false;
+            return name != null ? name.equals(item2.name) : item2.name == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = id;
+            result = 31 * result + (name != null ? name.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Database(version = 1, exportSchema = false, entities = {Item1.class, Item2.class})
+    public abstract static class ConflictDatabase extends RoomDatabase {
+        public abstract Item1.Store item1Dao();
+        public abstract Item2.Store item2Dao();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/DatabaseCallbackTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/DatabaseCallbackTest.java
new file mode 100644
index 0000000..5d48236
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/DatabaseCallbackTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.core.IsCollectionContaining.hasItem;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.vo.User;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class DatabaseCallbackTest {
+
+    @Test
+    @MediumTest
+    public void createAndOpen() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabaseCallback callback1 = new TestDatabaseCallback();
+        TestDatabase db1 = null;
+        TestDatabase db2 = null;
+        try {
+            db1 = Room.databaseBuilder(context, TestDatabase.class, "test")
+                    .addCallback(callback1)
+                    .build();
+            assertFalse(callback1.mCreated);
+            assertFalse(callback1.mOpened);
+            User user1 = TestUtil.createUser(3);
+            user1.setName("george");
+            db1.getUserDao().insert(user1);
+            assertTrue(callback1.mCreated);
+            assertTrue(callback1.mOpened);
+            TestDatabaseCallback callback2 = new TestDatabaseCallback();
+            db2 = Room.databaseBuilder(context, TestDatabase.class, "test")
+                    .addCallback(callback2)
+                    .build();
+            assertFalse(callback2.mCreated);
+            assertFalse(callback2.mOpened);
+            User user2 = db2.getUserDao().load(3);
+            assertThat(user2.getName(), is("george"));
+            assertFalse(callback2.mCreated); // Not called; already created by db1
+            assertTrue(callback2.mOpened);
+        } finally {
+            if (db1 != null) {
+                db1.close();
+            }
+            if (db2 != null) {
+                db2.close();
+            }
+            assertTrue(context.deleteDatabase("test"));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void writeOnCreate() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .addCallback(new RoomDatabase.Callback() {
+                    @Override
+                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
+                        Cursor cursor = null;
+                        try {
+                            cursor = db.query(
+                                    "SELECT name FROM sqlite_master WHERE type = 'table'");
+                            ArrayList<String> names = new ArrayList<>();
+                            while (cursor.moveToNext()) {
+                                names.add(cursor.getString(0));
+                            }
+                            assertThat(names, hasItem("User"));
+                        } finally {
+                            if (cursor != null) {
+                                cursor.close();
+                            }
+                        }
+                    }
+                })
+                .build();
+        List<Integer> ids = db.getUserDao().loadIds();
+        assertThat(ids, is(empty()));
+    }
+
+    public static class TestDatabaseCallback extends RoomDatabase.Callback {
+
+        boolean mCreated;
+        boolean mOpened;
+
+        @Override
+        public void onCreate(@NonNull SupportSQLiteDatabase db) {
+            mCreated = true;
+        }
+
+        @Override
+        public void onOpen(@NonNull SupportSQLiteDatabase db) {
+            mOpened = true;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EmbeddedTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EmbeddedTest.java
new file mode 100644
index 0000000..5d2400d
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EmbeddedTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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 androidx.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.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Room;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.PetCoupleDao;
+import androidx.room.integration.testapp.dao.PetDao;
+import androidx.room.integration.testapp.dao.SchoolDao;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.dao.UserPetDao;
+import androidx.room.integration.testapp.vo.Coordinates;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.PetCouple;
+import androidx.room.integration.testapp.vo.School;
+import androidx.room.integration.testapp.vo.SchoolRef;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndPet;
+import androidx.room.integration.testapp.vo.UserAndPetNonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class EmbeddedTest {
+    private UserDao mUserDao;
+    private PetDao mPetDao;
+    private UserPetDao mUserPetDao;
+    private SchoolDao mSchoolDao;
+    private PetCoupleDao mPetCoupleDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = db.getUserDao();
+        mPetDao = db.getPetDao();
+        mUserPetDao = db.getUserPetDao();
+        mSchoolDao = db.getSchoolDao();
+        mPetCoupleDao = db.getPetCoupleDao();
+    }
+
+    @Test
+    public void loadAll() {
+        Pet pet = TestUtil.createPet(1);
+        User user = TestUtil.createUser(2);
+        pet.setUserId(user.getId());
+        mUserDao.insert(user);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadAll();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void loadFromUsers() {
+        Pet pet = TestUtil.createPet(1);
+        User user = TestUtil.createUser(2);
+        pet.setUserId(user.getId());
+        mUserDao.insert(user);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadUsers();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void loadFromUsersWithNullPet() {
+        User user = TestUtil.createUser(2);
+        mUserDao.insert(user);
+        List<UserAndPet> all = mUserPetDao.loadUsers();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(nullValue()));
+    }
+
+    @Test
+    public void loadFromUsersWithNonNullPet() {
+        User user = TestUtil.createUser(2);
+        mUserDao.insert(user);
+        List<UserAndPetNonNull> all = mUserPetDao.loadUsersWithNonNullPet();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(new Pet()));
+    }
+
+    @Test
+    public void loadFromPets() {
+        Pet pet = TestUtil.createPet(1);
+        User user = TestUtil.createUser(2);
+        pet.setUserId(user.getId());
+        mUserDao.insert(user);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadPets();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(user));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void loadFromPetsWithNullUser() {
+        Pet pet = TestUtil.createPet(1);
+        mPetDao.insertOrReplace(pet);
+        List<UserAndPet> all = mUserPetDao.loadPets();
+        assertThat(all.size(), is(1));
+        assertThat(all.get(0).getUser(), is(nullValue()));
+        assertThat(all.get(0).getPet(), is(pet));
+    }
+
+    @Test
+    public void findSchoolByStreet() {
+        School school = TestUtil.createSchool(3, 5);
+        school.getAddress().setStreet("foo");
+        mSchoolDao.insert(school);
+        List<School> result = mSchoolDao.findByStreet("foo");
+        assertThat(result.size(), is(1));
+        assertThat(result.get(0), is(school));
+    }
+
+    @Test
+    public void loadSubFieldsAsPojo() throws Exception {
+        loadSubFieldsTest(new Callable<List<School>>() {
+            @Override
+            public List<School> call() throws Exception {
+                List<School> result = new ArrayList<>();
+                for (SchoolRef ref : mSchoolDao.schoolAndManagerNamesAsPojo()) {
+                    result.add(ref);
+                }
+                return result;
+            }
+        });
+    }
+
+    @Test
+    public void loadSubFieldsAsEntity() throws Exception {
+        loadSubFieldsTest(new Callable<List<School>>() {
+            @Override
+            public List<School> call() throws Exception {
+                return mSchoolDao.schoolAndManagerNames();
+            }
+        });
+    }
+
+    public void loadSubFieldsTest(Callable<List<School>> loader) throws Exception {
+        School school = TestUtil.createSchool(3, 5);
+        school.setName("MTV High");
+        school.getManager().setName("chet");
+        mSchoolDao.insert(school);
+
+        School school2 = TestUtil.createSchool(4, 6);
+        school2.setName("MTV Low");
+        school2.setManager(null);
+        mSchoolDao.insert(school2);
+
+        List<School> schools = loader.call();
+        assertThat(schools.size(), is(2));
+        assertThat(schools.get(0).getName(), is("MTV High"));
+        assertThat(schools.get(1).getName(), is("MTV Low"));
+        assertThat(schools.get(0).address, nullValue());
+        assertThat(schools.get(1).address, nullValue());
+        assertThat(schools.get(0).getManager(), notNullValue());
+        assertThat(schools.get(1).getManager(), nullValue());
+        assertThat(schools.get(0).getManager().getName(), is("chet"));
+    }
+
+    @Test
+    public void loadNestedSub() {
+        School school = TestUtil.createSchool(3, 5);
+        school.getAddress().getCoordinates().lat = 3.;
+        school.getAddress().getCoordinates().lng = 4.;
+        mSchoolDao.insert(school);
+        Coordinates coordinates = mSchoolDao.loadCoordinates(3);
+        assertThat(coordinates.lat, is(3.));
+        assertThat(coordinates.lng, is(4.));
+
+        School asSchool = mSchoolDao.loadCoordinatesAsSchool(3);
+        assertThat(asSchool.address.getCoordinates().lat, is(3.));
+        assertThat(asSchool.address.getCoordinates().lng, is(4.));
+        // didn't as for it so don't load
+        assertThat(asSchool.getManager(), nullValue());
+        assertThat(asSchool.address.getStreet(), nullValue());
+    }
+
+    @Test
+    public void sameFieldType() {
+        Pet male = TestUtil.createPet(3);
+        Pet female = TestUtil.createPet(5);
+        PetCouple petCouple = new PetCouple();
+        petCouple.id = "foo";
+        petCouple.male = male;
+        petCouple.setFemale(female);
+        mPetCoupleDao.insert(petCouple);
+        List<PetCouple> petCouples = mPetCoupleDao.loadAll();
+        assertThat(petCouples.size(), is(1));
+        PetCouple loaded = petCouples.get(0);
+        assertThat(loaded.id, is("foo"));
+        assertThat(loaded.male, is(male));
+        assertThat(loaded.getFemale(), is(female));
+    }
+
+    @Test
+    public void sameFieldOneNull() {
+        Pet loneWolf = TestUtil.createPet(3);
+        PetCouple petCouple = new PetCouple();
+        petCouple.id = "foo";
+        petCouple.male = loneWolf;
+        mPetCoupleDao.insert(petCouple);
+        List<PetCouple> petCouples = mPetCoupleDao.loadAll();
+        assertThat(petCouples.size(), is(1));
+        PetCouple loaded = petCouples.get(0);
+        assertThat(loaded.id, is("foo"));
+        assertThat(loaded.male, is(loneWolf));
+        assertThat(loaded.getFemale(), is(nullValue()));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ForeignKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ForeignKeyTest.java
new file mode 100644
index 0000000..b843387
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ForeignKeyTest.java
@@ -0,0 +1,426 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.either;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import android.database.sqlite.SQLiteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Delete;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ForeignKeyTest {
+    @Database(version = 1, entities = {A.class, B.class, C.class, D.class, E.class},
+            exportSchema = false)
+    abstract static class ForeignKeyDb extends RoomDatabase {
+        abstract FkDao dao();
+    }
+
+    @SuppressWarnings({"SqlNoDataSourceInspection", "SameParameterValue"})
+    @Dao
+    interface FkDao {
+        @Insert
+        void insert(A... a);
+
+        @Insert
+        void insert(B... b);
+
+        @Insert
+        void insert(C... c);
+
+        @Insert
+        void insert(D... d);
+
+        @Query("SELECT * FROM A WHERE id = :id")
+        A loadA(int id);
+
+        @Query("SELECT * FROM B WHERE id = :id")
+        B loadB(int id);
+
+        @Query("SELECT * FROM C WHERE id = :id")
+        C loadC(int id);
+
+        @Query("SELECT * FROM D WHERE id = :id")
+        D loadD(int id);
+
+        @Query("SELECT * FROM E WHERE id = :id")
+        E loadE(int id);
+
+        @Delete
+        void delete(A... a);
+
+        @Delete
+        void delete(B... b);
+
+        @Delete
+        void delete(C... c);
+
+        @Query("UPDATE A SET name = :newName WHERE id = :id")
+        void changeNameA(int id, String newName);
+
+        @Insert
+        void insert(E... e);
+
+
+    }
+
+    @Entity(indices = {@Index(value = "name", unique = true),
+            @Index(value = {"name", "lastName"}, unique = true)})
+    static class A {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String name;
+        public String lastName;
+
+        A(String name) {
+            this.name = name;
+        }
+
+        @Ignore
+        A(String name, String lastName) {
+            this.name = name;
+            this.lastName = lastName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = "name",
+                    childColumns = "aName")})
+
+    static class B {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+
+        B(String aName) {
+            this.aName = aName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = "name",
+                    childColumns = "aName",
+                    deferred = true)})
+    static class C {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+
+        C(String aName) {
+            this.aName = aName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = "name",
+                    childColumns = "aName",
+                    onDelete = ForeignKey.CASCADE,
+                    onUpdate = ForeignKey.CASCADE)})
+    static class D {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+
+        D(String aName) {
+            this.aName = aName;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity(foreignKeys = {
+            @ForeignKey(entity = A.class,
+                    parentColumns = {"name", "lastName"},
+                    childColumns = {"aName", "aLastName"},
+                    onDelete = ForeignKey.SET_NULL,
+                    onUpdate = ForeignKey.CASCADE)})
+    static class E {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String aName;
+        public String aLastName;
+
+        E() {
+        }
+
+        @Ignore
+        E(String aName, String aLastName) {
+            this.aName = aName;
+            this.aLastName = aLastName;
+        }
+    }
+
+
+    private ForeignKeyDb mDb;
+    private FkDao mDao;
+
+    @Before
+    public void openDb() {
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                ForeignKeyDb.class).build();
+        mDao = mDb.dao();
+    }
+
+    @Test
+    public void simpleForeignKeyFailure() {
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.insert(new B("foo"));
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void simpleForeignKeyDeferredFailure() {
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.insert(new C("foo"));
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void immediateForeignKeyFailure() {
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                try {
+                    mDb.beginTransaction();
+                    mDao.insert(new B("foo"));
+                    mDao.insert(new A("foo"));
+                    mDb.setTransactionSuccessful();
+                } finally {
+                    mDb.endTransaction();
+                }
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+    }
+
+    @Test
+    public void deferredForeignKeySuccess() {
+        try {
+            mDb.beginTransaction();
+            mDao.insert(new C("foo"));
+            mDao.insert(new A("foo"));
+            mDb.setTransactionSuccessful();
+        } finally {
+            mDb.endTransaction();
+        }
+        assertThat(mDao.loadA(1), notNullValue());
+        assertThat(mDao.loadC(1), notNullValue());
+    }
+
+    @Test
+    public void onDelete_noAction() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new B("a1"));
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.delete(a);
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_noAction_withTransaction() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new B("a1"));
+        final B b = mDao.loadB(1);
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                deleteInTransaction(a, b);
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_noAction_deferred() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new C("a1"));
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.delete(a);
+            }
+        });
+        assertThat(t, instanceOf(SQLiteException.class));
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_noAction__deferredWithTransaction() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new C("a1"));
+        final C c = mDao.loadC(1);
+        deleteInTransaction(a, c);
+    }
+
+    @Test
+    public void onDelete_cascade() {
+        mDao.insert(new A("a1"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new D("a1"));
+        final D d = mDao.loadD(1);
+        assertThat("test sanity", d, notNullValue());
+        mDao.delete(a);
+        assertThat(mDao.loadD(1), nullValue());
+    }
+
+    @Test
+    public void onUpdate_cascade() {
+        mDao.insert(new A("a1"));
+        mDao.insert(new D("a1"));
+        final D d = mDao.loadD(1);
+        assertThat("test sanity", d, notNullValue());
+        mDao.changeNameA(1, "bla");
+        assertThat(mDao.loadD(1).aName, equalTo("bla"));
+        assertThat(mDao.loadA(1).name, equalTo("bla"));
+    }
+
+    @Test
+    public void multipleReferences() {
+        mDao.insert(new A("a1", "a2"));
+        final A a = mDao.loadA(1);
+        assertThat("test sanity", a, notNullValue());
+        Throwable t = catchException(new ThrowingRunnable() {
+            @Override
+            public void run() throws Exception {
+                mDao.insert(new E("a1", "dsa"));
+            }
+        });
+        assertThat(t.getMessage().toUpperCase(Locale.US), is(foreignKeyErrorMessage()));
+    }
+
+    @Test
+    public void onDelete_setNull_multipleReferences() {
+        mDao.insert(new A("a1", "a2"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new E("a1", "a2"));
+        assertThat(mDao.loadE(1), notNullValue());
+        mDao.delete(a);
+        E e = mDao.loadE(1);
+        assertThat(e, notNullValue());
+        assertThat(e.aName, nullValue());
+        assertThat(e.aLastName, nullValue());
+    }
+
+    @Test
+    public void onUpdate_cascade_multipleReferences() {
+        mDao.insert(new A("a1", "a2"));
+        final A a = mDao.loadA(1);
+        mDao.insert(new E("a1", "a2"));
+        assertThat(mDao.loadE(1), notNullValue());
+        mDao.changeNameA(1, "foo");
+        assertThat(mDao.loadE(1), notNullValue());
+        assertThat(mDao.loadE(1).aName, equalTo("foo"));
+        assertThat(mDao.loadE(1).aLastName, equalTo("a2"));
+    }
+
+    private static Matcher<String> foreignKeyErrorMessage() {
+        return either(containsString("FOREIGN KEY"))
+                .or(both(containsString("CODE 19")).and(containsString("CONSTRAINT FAILED")));
+    }
+
+    @SuppressWarnings("Duplicates")
+    private void deleteInTransaction(A a, B b) {
+        mDb.beginTransaction();
+        try {
+            mDao.delete(a);
+            mDao.delete(b);
+            mDb.setTransactionSuccessful();
+        } finally {
+            mDb.endTransaction();
+        }
+    }
+
+    @SuppressWarnings("Duplicates")
+    private void deleteInTransaction(A a, C c) {
+        mDb.beginTransaction();
+        try {
+            mDao.delete(a);
+            mDao.delete(c);
+            mDb.setTransactionSuccessful();
+        } finally {
+            mDb.endTransaction();
+        }
+    }
+
+    private static Throwable catchException(ThrowingRunnable throwingRunnable) {
+        try {
+            throwingRunnable.run();
+        } catch (Throwable t) {
+            return t;
+        }
+        throw new RuntimeException("didn't throw an exception");
+    }
+
+    private interface ThrowingRunnable {
+        void run() throws Exception;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/FunnyNamedDaoTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/FunnyNamedDaoTest.java
new file mode 100644
index 0000000..9d0da7f
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/FunnyNamedDaoTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 androidx.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.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
+import androidx.room.integration.testapp.vo.FunnyNamedEntity;
+
+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];
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> mFunnyNamedDao.observableOne(2).observeForever(
+                        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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/GenericEntityTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/GenericEntityTest.java
new file mode 100644
index 0000000..6d7acae
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/GenericEntityTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GenericEntityTest {
+    private GenericDb mDb;
+    private GenericDao mDao;
+
+    @Before
+    public void init() {
+        mDb = Room.inMemoryDatabaseBuilder(
+                InstrumentationRegistry.getTargetContext(),
+                GenericDb.class
+        ).build();
+        mDao = mDb.getDao();
+    }
+
+    @After
+    public void close() {
+        mDb.close();
+    }
+
+    @Test
+    public void readWriteEntity() {
+        EntityItem item = new EntityItem("abc", "def");
+        mDao.insert(item);
+        EntityItem received = mDao.get("abc");
+        assertThat(received, is(item));
+    }
+
+    @Test
+    public void readPojo() {
+        EntityItem item = new EntityItem("abc", "def");
+        mDao.insert(item);
+        PojoItem received = mDao.getPojo("abc");
+        assertThat(received.id, is("abc"));
+    }
+
+    static class Item<P, F> {
+        @NonNull
+        @PrimaryKey
+        public final P id;
+        private F mField;
+
+        Item(@NonNull P id) {
+            this.id = id;
+        }
+
+        public F getField() {
+            return mField;
+        }
+
+        public void setField(F field) {
+            mField = field;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Item<?, ?> item = (Item<?, ?>) o;
+            //noinspection SimplifiableIfStatement
+            if (!id.equals(item.id)) return false;
+            return mField != null ? mField.equals(item.mField) : item.mField == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = id.hashCode();
+            result = 31 * result + (mField != null ? mField.hashCode() : 0);
+            return result;
+        }
+    }
+
+    static class PojoItem extends Item<String, Integer> {
+        PojoItem(String id) {
+            super(id);
+        }
+    }
+
+    @Entity
+    static class EntityItem extends Item<String, Integer> {
+        public final String name;
+
+        EntityItem(String id, String name) {
+            super(id);
+            this.name = name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            if (!super.equals(o)) return false;
+            EntityItem that = (EntityItem) o;
+            return name != null ? name.equals(that.name) : that.name == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = super.hashCode();
+            result = 31 * result + (name != null ? name.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Dao
+    public interface GenericDao {
+        @Insert
+        void insert(EntityItem... items);
+
+        @Query("SELECT * FROM EntityItem WHERE id = :id")
+        EntityItem get(String id);
+
+        @Query("SELECT * FROM EntityItem WHERE id = :id")
+        PojoItem getPojo(String id);
+    }
+
+    @Database(version = 1, entities = {EntityItem.class}, exportSchema = false)
+    public abstract static class GenericDb extends RoomDatabase {
+        abstract GenericDao getDao();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/IdentityDetectionTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/IdentityDetectionTest.java
new file mode 100644
index 0000000..feac6cd
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/IdentityDetectionTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import androidx.room.Room;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.vo.User;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IdentityDetectionTest {
+    static final String TAG = "IdentityDetectionTest";
+    static final String DB_FILE_NAME = "identity_test_db";
+    TestDatabase mTestDatabase;
+    @Before
+    public void createTestDatabase() {
+        deleteDbFile();
+    }
+
+    @Test
+    public void reOpenWithoutIssues() {
+        openDb();
+        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
+        closeDb();
+        openDb();
+        User[] users = mTestDatabase.getUserDao().loadByIds(3);
+        assertThat(users.length, is(1));
+    }
+
+    @Test
+    public void reOpenChangedHash() {
+        openDb();
+        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
+        // change the hash
+        SupportSQLiteDatabase db = mTestDatabase.getOpenHelper().getWritableDatabase();
+        db.execSQL("UPDATE " + Room.MASTER_TABLE_NAME + " SET `identity_hash` = ?"
+                + " WHERE id = 42", new String[]{"bad hash"});
+        closeDb();
+        Throwable[] exceptions = new Throwable[1];
+        try {
+            openDb();
+            mTestDatabase.getUserDao().loadByIds(3);
+        } catch (Throwable t) {
+            exceptions[0] = t;
+            mTestDatabase = null;
+        }
+        assertThat(exceptions[0], instanceOf(IllegalStateException.class));
+    }
+
+    @Test
+    public void reOpenMasterTableDropped() {
+        openDb();
+        mTestDatabase.getUserDao().insert(TestUtil.createUser(3));
+        // drop the master table
+        SupportSQLiteDatabase db = mTestDatabase.getOpenHelper().getWritableDatabase();
+        db.execSQL("DROP TABLE " + Room.MASTER_TABLE_NAME);
+        closeDb();
+        try {
+            openDb();
+            mTestDatabase.getUserDao().loadByIds(3);
+            fail("Was expecting an exception.");
+        } catch (Throwable t) {
+            assertThat(t, instanceOf(IllegalStateException.class));
+        }
+    }
+
+    private void closeDb() {
+        mTestDatabase.close();
+    }
+
+    private void openDb() {
+        mTestDatabase = Room.databaseBuilder(InstrumentationRegistry.getTargetContext(),
+                TestDatabase.class, DB_FILE_NAME).build();
+    }
+
+    @After
+    public void clear() {
+        try {
+            if (mTestDatabase != null) {
+                closeDb();
+            }
+            deleteDbFile();
+        } catch (Throwable t) {
+            Log.e(TAG, "could not close test database", t);
+            throw t;
+        }
+    }
+
+    private void deleteDbFile() {
+        File testDb = InstrumentationRegistry.getTargetContext().getDatabasePath(DB_FILE_NAME);
+        testDb.delete();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/IndexingTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/IndexingTest.java
new file mode 100644
index 0000000..04fc428
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/IndexingTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IndexingTest {
+    @Entity(
+            tableName = "foo_table",
+            indices = {
+                    @Index({"field1", "field2"}),
+                    @Index(value = {"field2", "mId"}, unique = true),
+                    @Index(value = {"field2"}, unique = true, name = "customIndex"),
+            })
+    static class Entity1 {
+        @PrimaryKey
+        public int mId;
+        public String field1;
+        public String field2;
+        @ColumnInfo(index = true, name = "my_field")
+        public String field3;
+    }
+
+    static class IndexInfo {
+        public String name;
+        @ColumnInfo(name = "tbl_name")
+        public String tableName;
+        public String sql;
+    }
+
+    @Dao
+    public interface SqlMasterDao {
+        @Query("SELECT * FROM sqlite_master WHERE type = 'index'")
+        List<IndexInfo> loadIndices();
+    }
+
+    @Database(entities = {Entity1.class}, version = 1, exportSchema = false)
+    abstract static class IndexingDb extends RoomDatabase {
+        abstract SqlMasterDao sqlMasterDao();
+    }
+
+    @Test
+    public void verifyIndices() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        IndexingDb db = Room.inMemoryDatabaseBuilder(context, IndexingDb.class).build();
+        List<IndexInfo> indices = db.sqlMasterDao().loadIndices();
+        assertThat(indices.size(), is(4));
+        for (IndexInfo info : indices) {
+            assertThat(info.tableName, is("foo_table"));
+        }
+        assertThat(indices.get(0).sql, is("CREATE INDEX `index_foo_table_field1_field2`"
+                + " ON `foo_table` (`field1`, `field2`)"));
+        assertThat(indices.get(1).sql, is("CREATE UNIQUE INDEX `index_foo_table_field2_mId`"
+                + " ON `foo_table` (`field2`, `mId`)"));
+        assertThat(indices.get(2).sql, is("CREATE UNIQUE INDEX `customIndex`"
+                + " ON `foo_table` (`field2`)"));
+        assertThat(indices.get(3).sql, is("CREATE INDEX `index_foo_table_my_field`"
+                + " ON `foo_table` (`my_field`)"));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTest.java
new file mode 100644
index 0000000..fe66855
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
+import androidx.room.InvalidationTracker;
+import androidx.room.Room;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.vo.User;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests invalidation tracking.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InvalidationTest {
+    @Rule
+    public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule();
+    private UserDao mUserDao;
+    private TestDatabase mDb;
+
+    @Before
+    public void createDb() throws TimeoutException, InterruptedException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = mDb.getUserDao();
+        drain();
+    }
+
+    @After
+    public void closeDb() throws TimeoutException, InterruptedException {
+        mDb.close();
+        drain();
+    }
+
+    private void drain() throws TimeoutException, InterruptedException {
+        executorRule.drainTasks(1, TimeUnit.MINUTES);
+    }
+
+    @Test
+    public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        LoggingObserver observer = new LoggingObserver("User");
+        mDb.getInvalidationTracker().addObserver(observer);
+        drain();
+        mUserDao.updateById(3, "foo2");
+        drain();
+        assertThat(observer.getInvalidatedTables(), hasSize(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("User"));
+    }
+
+    @Test
+    public void testInvalidationOnDelete() throws InterruptedException, TimeoutException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        LoggingObserver observer = new LoggingObserver("User");
+        mDb.getInvalidationTracker().addObserver(observer);
+        drain();
+        mUserDao.delete(user);
+        drain();
+        assertThat(observer.getInvalidatedTables(), hasSize(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("User"));
+    }
+
+    @Test
+    public void testInvalidationOnInsert() throws InterruptedException, TimeoutException {
+        LoggingObserver observer = new LoggingObserver("User");
+        mDb.getInvalidationTracker().addObserver(observer);
+        drain();
+        mUserDao.insert(TestUtil.createUser(3));
+        drain();
+        assertThat(observer.getInvalidatedTables(), hasSize(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("User"));
+    }
+
+    @Test
+    public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException {
+        LoggingObserver observer = new LoggingObserver("User");
+        mUserDao.insert(TestUtil.createUser(3));
+        drain();
+        mDb.getInvalidationTracker().addObserver(observer);
+        drain();
+        assertThat(observer.getInvalidatedTables(), nullValue());
+    }
+
+    @Test
+    public void testMultipleTables() throws InterruptedException, TimeoutException {
+        LoggingObserver observer = new LoggingObserver("User", "Pet");
+        mDb.getInvalidationTracker().addObserver(observer);
+        drain();
+        mUserDao.insert(TestUtil.createUser(3));
+        drain();
+        assertThat(observer.getInvalidatedTables(), hasSize(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("User"));
+    }
+
+    private static class LoggingObserver extends InvalidationTracker.Observer {
+        private Set<String> mInvalidatedTables;
+
+        LoggingObserver(String... tables) {
+            super(tables);
+        }
+
+        @Override
+        public void onInvalidated(@NonNull Set<String> tables) {
+            mInvalidatedTables = tables;
+        }
+
+        Set<String> getInvalidatedTables() {
+            return mInvalidatedTables;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/LiveDataQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/LiveDataQueryTest.java
new file mode 100644
index 0000000..2b32a62
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/LiveDataQueryTest.java
@@ -0,0 +1,406 @@
+/*
+ * 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 androidx.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.os.Build;
+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;
+
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.room.InvalidationTrackerTrojan;
+import androidx.room.integration.testapp.vo.AvgWeightByAge;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.PetsToys;
+import androidx.room.integration.testapp.vo.Toy;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests invalidation tracking.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LiveDataQueryTest extends TestDatabaseTest {
+    @Rule
+    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+
+    @Test
+    public void observeById() throws InterruptedException, ExecutionException, TimeoutException {
+        final LiveData<User> userLiveData = mUserDao.liveUserById(5);
+        final TestLifecycleOwner testOwner = new TestLifecycleOwner();
+        testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
+        final TestObserver<User> observer = new TestObserver<>();
+        observe(userLiveData, testOwner, observer);
+        assertThat(observer.hasValue(), is(false));
+        observer.reset();
+
+        testOwner.handleEvent(Lifecycle.Event.ON_START);
+        assertThat(observer.get(), is(nullValue()));
+
+        // another id
+        observer.reset();
+        mUserDao.insert(TestUtil.createUser(7));
+        assertThat(observer.get(), is(nullValue()));
+
+        observer.reset();
+        final User u5 = TestUtil.createUser(5);
+        mUserDao.insert(u5);
+        assertThat(observer.get(), is(notNullValue()));
+
+        u5.setName("foo-foo-foo");
+        observer.reset();
+        mUserDao.insertOrReplace(u5);
+        final User updated = observer.get();
+        assertThat(updated, is(notNullValue()));
+        assertThat(updated.getName(), is("foo-foo-foo"));
+
+        testOwner.handleEvent(Lifecycle.Event.ON_STOP);
+        observer.reset();
+        u5.setName("baba");
+        mUserDao.insertOrReplace(u5);
+        assertThat(observer.hasValue(), is(false));
+    }
+
+    @Test
+    public void observeListQuery() throws InterruptedException, ExecutionException,
+            TimeoutException {
+        final LiveData<List<User>> userLiveData = mUserDao.liveUsersListByName("frida");
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final TestObserver<List<User>> observer = new TestObserver<>();
+        observe(userLiveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(Collections.<User>emptyList()));
+
+        observer.reset();
+        final User user1 = TestUtil.createUser(3);
+        user1.setName("dog frida");
+        mUserDao.insert(user1);
+        assertThat(observer.get(), is(Collections.singletonList(user1)));
+
+        observer.reset();
+        final User user2 = TestUtil.createUser(5);
+        user2.setName("does not match");
+        mUserDao.insert(user2);
+        assertThat(observer.get(), is(Collections.singletonList(user1)));
+
+        observer.reset();
+        user1.setName("i don't match either");
+        mUserDao.insertOrReplace(user1);
+        assertThat(observer.get(), is(Collections.<User>emptyList()));
+
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP);
+
+        observer.reset();
+        final User user3 = TestUtil.createUser(9);
+        user3.setName("painter frida");
+        mUserDao.insertOrReplace(user3);
+        assertThat(observer.hasValue(), is(false));
+
+        observer.reset();
+        final User user4 = TestUtil.createUser(11);
+        user4.setName("friday");
+        mUserDao.insertOrReplace(user4);
+        assertThat(observer.hasValue(), is(false));
+
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        assertThat(observer.get(), is(Arrays.asList(user4, user3)));
+    }
+
+    @Test
+    public void liveDataWithPojo() throws ExecutionException, InterruptedException,
+            TimeoutException {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
+        users[0].setAge(10);
+        users[0].setWeight(15);
+
+        users[1].setAge(20);
+        users[1].setWeight(25);
+
+        users[2].setAge(20);
+        users[2].setWeight(26);
+
+        users[3].setAge(10);
+        users[3].setWeight(21);
+
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+
+        final TestObserver<AvgWeightByAge> observer = new TestObserver<>();
+        LiveData<AvgWeightByAge> liveData = mUserDao.maxWeightByAgeGroup();
+
+        observe(liveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+
+        observer.reset();
+        mUserDao.insertAll(users);
+        assertThat(observer.get(), is(new AvgWeightByAge(20, 25.5f)));
+
+        observer.reset();
+        User user3 = mUserDao.load(3);
+        user3.setWeight(79);
+        mUserDao.insertOrReplace(user3);
+
+        assertThat(observer.get(), is(new AvgWeightByAge(10, 50)));
+    }
+
+    @Test
+    public void withRelation() throws ExecutionException, InterruptedException, TimeoutException {
+        final LiveData<UserAndAllPets> liveData = mUserPetDao.liveUserWithPets(3);
+        final TestObserver<UserAndAllPets> observer = new TestObserver<>();
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        observe(liveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+
+        observer.reset();
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        final UserAndAllPets noPets = observer.get();
+        assertThat(noPets.user, is(user));
+
+        observer.reset();
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mPetDao.insertAll(pets);
+
+        final UserAndAllPets withPets = observer.get();
+        assertThat(withPets.user, is(user));
+        assertThat(withPets.pets, is(Arrays.asList(pets)));
+    }
+
+    @Test
+    public void withRelationOnly() throws ExecutionException, InterruptedException,
+            TimeoutException {
+        LiveData<PetsToys> liveData = mSpecificDogDao.getSpecificDogsToys();
+
+        PetsToys expected = new PetsToys();
+        expected.petId = 123;
+
+        Toy toy = new Toy();
+        toy.setId(1);
+        toy.setPetId(123);
+        toy.setName("ball");
+
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final TestObserver<PetsToys> observer = new TestObserver<>();
+        observe(liveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(expected));
+
+        observer.reset();
+        expected.toys.add(toy);
+        mToyDao.insert(toy);
+        assertThat(observer.get(), is(expected));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    public void withWithClause() throws ExecutionException, InterruptedException,
+            TimeoutException {
+        LiveData<List<String>> actual =
+                mWithClauseDao.getUsersWithFactorialIdsLiveData(0);
+        List<String> expected = new ArrayList<>();
+
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final TestObserver<List<String>> observer = new TestObserver<>();
+        observe(actual, lifecycleOwner, observer);
+        assertThat(observer.get(), is(expected));
+
+        observer.reset();
+        User user = new User();
+        user.setId(0);
+        user.setName("Zero");
+        mUserDao.insert(user);
+        assertThat(observer.get(), is(expected));
+
+        observer.reset();
+        user = new User();
+        user.setId(1);
+        user.setName("One");
+        mUserDao.insert(user);
+        expected.add("One");
+        assertThat(observer.get(), is(expected));
+
+        observer.reset();
+        user = new User();
+        user.setId(6);
+        user.setName("Six");
+        mUserDao.insert(user);
+        assertThat(observer.get(), is(expected));
+
+        actual = mWithClauseDao.getUsersWithFactorialIdsLiveData(3);
+        observe(actual, lifecycleOwner, observer);
+        expected.add("Six");
+        assertThat(observer.get(), is(expected));
+    }
+
+    @MediumTest
+    @Test
+    public void handleGc() throws ExecutionException, InterruptedException, TimeoutException {
+        LiveData<User> liveData = mUserDao.liveUserById(3);
+        final TestObserver<User> observer = new TestObserver<>();
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        observe(liveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+        observer.reset();
+        final User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        assertThat(observer.get(), is(notNullValue()));
+        observer.reset();
+        forceGc();
+        String name = UUID.randomUUID().toString();
+        mUserDao.updateById(3, name);
+        assertThat(observer.get().getName(), is(name));
+
+        // release references
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                lifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
+            }
+        });
+        WeakReference<LiveData> weakLiveData = new WeakReference<LiveData>(liveData);
+        //noinspection UnusedAssignment
+        liveData = null;
+        forceGc();
+        mUserDao.updateById(3, "Bar");
+        forceGc();
+        assertThat(InvalidationTrackerTrojan.countObservers(mDatabase.getInvalidationTracker()),
+                is(0));
+        assertThat(weakLiveData.get(), nullValue());
+    }
+
+    @Test
+    public void booleanLiveData() throws ExecutionException, InterruptedException,
+            TimeoutException {
+        User user = TestUtil.createUser(3);
+        user.setAdmin(false);
+        LiveData<Boolean> adminLiveData = mUserDao.isAdminLiveData(3);
+        final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+        lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+        final TestObserver<Boolean> observer = new TestObserver<>();
+        observe(adminLiveData, lifecycleOwner, observer);
+        assertThat(observer.get(), is(nullValue()));
+        mUserDao.insert(user);
+        assertThat(observer.get(), is(false));
+        user.setAdmin(true);
+        mUserDao.insertOrReplace(user);
+        assertThat(observer.get(), is(true));
+    }
+
+    private void observe(final LiveData liveData, final LifecycleOwner provider,
+            final Observer observer) throws ExecutionException, InterruptedException {
+        FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                //noinspection unchecked
+                liveData.observe(provider, observer);
+                return null;
+            }
+        });
+        ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
+        futureTask.get();
+    }
+
+    private void drain() throws TimeoutException, InterruptedException {
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+    }
+
+    private static void forceGc() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+    }
+
+    static class TestLifecycleOwner implements LifecycleOwner {
+
+        private LifecycleRegistry mLifecycle;
+
+        TestLifecycleOwner() {
+            mLifecycle = new LifecycleRegistry(this);
+        }
+
+        @Override
+        public Lifecycle getLifecycle() {
+            return mLifecycle;
+        }
+
+        void handleEvent(Lifecycle.Event event) {
+            mLifecycle.handleLifecycleEvent(event);
+        }
+    }
+
+    private class TestObserver<T> implements Observer<T> {
+        private T mLastData;
+        private boolean mHasValue = false;
+
+        void reset() {
+            mHasValue = false;
+            mLastData = null;
+        }
+
+        @Override
+        public void onChanged(@Nullable T o) {
+            mLastData = o;
+            mHasValue = true;
+        }
+
+        boolean hasValue() throws TimeoutException, InterruptedException {
+            drain();
+            return mHasValue;
+        }
+
+        T get() throws InterruptedException, TimeoutException {
+            drain();
+            assertThat(hasValue(), is(true));
+            return mLastData;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MainThreadCheckTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MainThreadCheckTest.java
new file mode 100644
index 0000000..50ffe3f
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MainThreadCheckTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.arch.core.util.Function;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.integration.testapp.TestDatabase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MainThreadCheckTest {
+
+    @Test
+    public void testMainThread() {
+        final Throwable error = test(false, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().load(3);
+                return null;
+            }
+        });
+        assertThat(error, notNullValue());
+        assertThat(error, instanceOf(IllegalStateException.class));
+    }
+
+    @Test
+    public void testFlowableOnMainThread() {
+        final Throwable error = test(false, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().flowableUserById(3);
+                return null;
+            }
+        });
+        assertThat(error, nullValue());
+    }
+
+    @Test
+    public void testLiveDataOnMainThread() {
+        final Throwable error = test(false, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().liveUserById(3);
+                return null;
+            }
+        });
+        assertThat(error, nullValue());
+    }
+
+    @Test
+    public void testAllowMainThread() {
+        final Throwable error = test(true, new Function<TestDatabase, Void>() {
+            @Override
+            public Void apply(TestDatabase db) {
+                db.getUserDao().load(3);
+                return null;
+            }
+        });
+        assertThat(error, nullValue());
+    }
+
+    private Throwable test(boolean allowMainThread, final Function<TestDatabase, Void> fun) {
+        Context context = InstrumentationRegistry.getTargetContext();
+        final RoomDatabase.Builder<TestDatabase> builder = Room.inMemoryDatabaseBuilder(
+                context, TestDatabase.class);
+        if (allowMainThread) {
+            builder.allowMainThreadQueries();
+        }
+        final TestDatabase db = builder.build();
+        final AtomicReference<Throwable> error = new AtomicReference<>();
+        try {
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        fun.apply(db);
+                    } catch (Throwable t) {
+                        error.set(t);
+                    }
+                }
+            });
+        } finally {
+            db.close();
+        }
+        return error.get();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java
new file mode 100644
index 0000000..9678d14
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Room;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.vo.AvgWeightByAge;
+import androidx.room.integration.testapp.vo.User;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class PojoTest {
+    private UserDao mUserDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = db.getUserDao();
+    }
+
+    @Test
+    public void weightsByAge() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 10);
+        users[0].setAge(10);
+        users[0].setWeight(20);
+
+        users[1].setAge(10);
+        users[1].setWeight(30);
+
+        users[2].setAge(15);
+        users[2].setWeight(12);
+
+        users[3].setAge(35);
+        users[3].setWeight(55);
+
+        mUserDao.insertAll(users);
+        assertThat(mUserDao.weightByAge(), is(
+                Arrays.asList(
+                        new AvgWeightByAge(35, 55),
+                        new AvgWeightByAge(10, 25),
+                        new AvgWeightByAge(15, 12)
+                )
+        ));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoWithRelationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoWithRelationTest.java
new file mode 100644
index 0000000..b09584c
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoWithRelationTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.integration.testapp.vo.EmbeddedUserAndAllPets;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.PetWithToyIds;
+import androidx.room.integration.testapp.vo.Toy;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+import androidx.room.integration.testapp.vo.UserAndPetAdoptionDates;
+import androidx.room.integration.testapp.vo.UserIdAndPetNames;
+import androidx.room.integration.testapp.vo.UserWithPetsAndToys;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PojoWithRelationTest extends TestDatabaseTest {
+    @Test
+    public void fetchAll() {
+        User[] users = TestUtil.createUsersArray(1, 2, 3);
+        Pet[][] userPets = new Pet[3][];
+        mUserDao.insertAll(users);
+        for (User user : users) {
+            Pet[] pets = TestUtil.createPetsForUser(user.getId(), user.getId() * 10,
+                    user.getId() - 1);
+            mPetDao.insertAll(pets);
+            userPets[user.getId() - 1] = pets;
+        }
+        List<UserAndAllPets> usersAndPets = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(usersAndPets.size(), is(3));
+        assertThat(usersAndPets.get(0).user, is(users[0]));
+        assertThat(usersAndPets.get(0).pets, is(Collections.<Pet>emptyList()));
+
+        assertThat(usersAndPets.get(1).user, is(users[1]));
+        assertThat(usersAndPets.get(1).pets, is(Arrays.asList(userPets[1])));
+
+        assertThat(usersAndPets.get(2).user, is(users[2]));
+        assertThat(usersAndPets.get(2).pets, is(Arrays.asList(userPets[2])));
+    }
+
+    private void createData() {
+        User[] users = TestUtil.createUsersArray(1, 2);
+        mUserDao.insertAll(users);
+        Pet user1_pet1 = TestUtil.createPet(1);
+        user1_pet1.setUserId(1);
+        user1_pet1.setName("pet1");
+        mPetDao.insertOrReplace(user1_pet1);
+
+        Pet user1_pet2 = TestUtil.createPet(2);
+        user1_pet2.setUserId(1);
+        user1_pet2.setName("pet2");
+        mPetDao.insertOrReplace(user1_pet2);
+
+        Pet user2_pet1 = TestUtil.createPet(3);
+        user2_pet1.setUserId(2);
+        user2_pet1.setName("pet3");
+        mPetDao.insertOrReplace(user2_pet1);
+    }
+
+    @Test
+    public void fetchWithNames() {
+        createData();
+
+        List<UserIdAndPetNames> usersAndPets = mUserPetDao.loadUserAndPetNames();
+        assertThat(usersAndPets.size(), is(2));
+        assertThat(usersAndPets.get(0).userId, is(1));
+        assertThat(usersAndPets.get(1).userId, is(2));
+        assertThat(usersAndPets.get(0).names, is(Arrays.asList("pet1", "pet2")));
+        assertThat(usersAndPets.get(1).names, is(Collections.singletonList("pet3")));
+    }
+
+    @Test
+    public void nested() {
+        createData();
+        Toy pet1_toy1 = new Toy();
+        pet1_toy1.setName("toy1");
+        pet1_toy1.setPetId(1);
+        Toy pet1_toy2 = new Toy();
+        pet1_toy2.setName("toy2");
+        pet1_toy2.setPetId(1);
+        mToyDao.insert(pet1_toy1, pet1_toy2);
+        List<UserWithPetsAndToys> userWithPetsAndToys = mUserPetDao.loadUserWithPetsAndToys();
+        assertThat(userWithPetsAndToys.size(), is(2));
+        UserWithPetsAndToys first = userWithPetsAndToys.get(0);
+        List<Toy> toys = first.pets.get(0).toys;
+        assertThat(toys.get(0).getName(), is("toy1"));
+        assertThat(toys.get(1).getName(), is("toy2"));
+        assertThat(userWithPetsAndToys.get(1).pets.get(0).toys, is(Collections.<Toy>emptyList()));
+    }
+
+    @Test
+    public void duplicateParentField() {
+        User[] users = TestUtil.createUsersArray(1, 2);
+        Pet[] pets_1 = TestUtil.createPetsForUser(1, 1, 2);
+        Pet[] pets_2 = TestUtil.createPetsForUser(2, 10, 1);
+        mUserDao.insertAll(users);
+        mPetDao.insertAll(pets_1);
+        mPetDao.insertAll(pets_2);
+        List<UserAndAllPets> userAndAllPets = mUserPetDao.unionByItself();
+        assertThat(userAndAllPets.size(), is(4));
+        for (int i = 0; i < 4; i++) {
+            assertThat("user at " + i, userAndAllPets.get(i).user, is(users[i % 2]));
+        }
+        assertThat(userAndAllPets.get(0).pets, is(Arrays.asList(pets_1)));
+        assertThat(userAndAllPets.get(2).pets, is(Arrays.asList(pets_1)));
+
+        assertThat(userAndAllPets.get(1).pets, is(Arrays.asList(pets_2)));
+        assertThat(userAndAllPets.get(3).pets, is(Arrays.asList(pets_2)));
+    }
+
+    @Test
+    public void embeddedRelation() {
+        createData();
+        EmbeddedUserAndAllPets relationContainer = mUserPetDao.loadUserAndPetsAsEmbedded(1);
+        assertThat(relationContainer.getUserAndAllPets(), notNullValue());
+        assertThat(relationContainer.getUserAndAllPets().user.getId(), is(1));
+        assertThat(relationContainer.getUserAndAllPets().pets.size(), is(2));
+    }
+
+    @Test
+    public void boxedPrimitiveList() {
+        Pet pet1 = TestUtil.createPet(3);
+        Pet pet2 = TestUtil.createPet(5);
+
+        Toy pet1_toy1 = TestUtil.createToyForPet(pet1, 10);
+        Toy pet1_toy2 = TestUtil.createToyForPet(pet1, 20);
+        Toy pet2_toy1 = TestUtil.createToyForPet(pet2, 30);
+
+        mPetDao.insertOrReplace(pet1, pet2);
+        mToyDao.insert(pet1_toy1, pet1_toy2, pet2_toy1);
+
+        List<PetWithToyIds> petWithToyIds = mPetDao.allPetsWithToyIds();
+        //noinspection ArraysAsListWithZeroOrOneArgument
+        assertThat(petWithToyIds, is(
+                Arrays.asList(
+                        new PetWithToyIds(pet1, Arrays.asList(10, 20)),
+                        new PetWithToyIds(pet2, Arrays.asList(30)))
+        ));
+    }
+
+    @Test
+    public void viaTypeConverter() {
+        User user = TestUtil.createUser(3);
+        Pet pet1 = TestUtil.createPet(3);
+        Date date1 = new Date(300);
+        pet1.setAdoptionDate(date1);
+        Pet pet2 = TestUtil.createPet(5);
+        Date date2 = new Date(700);
+        pet2.setAdoptionDate(date2);
+
+        pet1.setUserId(3);
+        pet2.setUserId(3);
+        mUserDao.insert(user);
+        mPetDao.insertOrReplace(pet1, pet2);
+
+        List<UserAndPetAdoptionDates> adoptions =
+                mUserPetDao.loadUserWithPetAdoptionDates();
+
+        assertThat(adoptions, is(Arrays.asList(
+                new UserAndPetAdoptionDates(user, Arrays.asList(new Date(300), new Date(700)))
+        )));
+    }
+
+    @Test
+    public void largeRelation_child() {
+        User user = TestUtil.createUser(3);
+        List<Pet> pets = new ArrayList<>();
+        for (int i = 0; i < 2000; i++) {
+            Pet pet = TestUtil.createPet(i + 1);
+            pet.setUserId(3);
+        }
+        mUserDao.insert(user);
+        mPetDao.insertAll(pets.toArray(new Pet[pets.size()]));
+        List<UserAndAllPets> result = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(result.size(), is(1));
+        assertThat(result.get(0).user, is(user));
+        assertThat(result.get(0).pets, is(pets));
+    }
+
+    @Test
+    public void largeRelation_parent() {
+        final List<User> users = new ArrayList<>();
+        final List<Pet> pets = new ArrayList<>();
+        for (int i = 0; i < 2000; i++) {
+            User user = TestUtil.createUser(i + 1);
+            users.add(user);
+            Pet pet = TestUtil.createPet(i + 1);
+            pet.setUserId(user.getId());
+            pets.add(pet);
+        }
+        mDatabase.runInTransaction(new Runnable() {
+            @Override
+            public void run() {
+                mUserDao.insertAll(users.toArray(new User[users.size()]));
+                mPetDao.insertAll(pets.toArray(new Pet[pets.size()]));
+            }
+        });
+        List<UserAndAllPets> result = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(result.size(), is(2000));
+        for (int i = 0; i < 2000; i++) {
+            assertThat(result.get(i).user, is(users.get(i)));
+            assertThat(result.get(i).pets, is(Collections.singletonList(pets.get(i))));
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrimaryKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrimaryKeyTest.java
new file mode 100644
index 0000000..8eea68b
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrimaryKeyTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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 androidx.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 static org.junit.Assert.assertNotNull;
+
+import android.database.sqlite.SQLiteConstraintException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Room;
+import androidx.room.integration.testapp.PKeyTestDatabase;
+import androidx.room.integration.testapp.vo.IntAutoIncPKeyEntity;
+import androidx.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import androidx.room.integration.testapp.vo.IntegerPKeyEntity;
+import androidx.room.integration.testapp.vo.ObjectPKeyEntity;
+
+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(),
+                PKeyTestDatabase.class).build();
+    }
+
+    @Test
+    public void integerTest() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.data = "foo";
+        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(1);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void dontOverrideNullable0() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.pKey = 0;
+        entity.data = "foo";
+        mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+        IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(0);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void intTest() {
+        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
+        entity.data = "foo";
+        mDatabase.intPKeyDao().insertMe(entity);
+        IntAutoIncPKeyEntity loaded = mDatabase.intPKeyDao().getMe(1);
+        assertThat(loaded, notNullValue());
+        assertThat(loaded.data, is(entity.data));
+    }
+
+    @Test
+    public void getInsertedId() {
+        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
+        entity.data = "foo";
+        final long id = mDatabase.intPKeyDao().insertAndGetId(entity);
+        assertThat(mDatabase.intPKeyDao().getMe((int) id).data, is("foo"));
+    }
+
+    @Test
+    public void getInsertedIds() {
+        IntAutoIncPKeyEntity entity = new IntAutoIncPKeyEntity();
+        entity.data = "foo";
+        IntAutoIncPKeyEntity entity2 = new IntAutoIncPKeyEntity();
+        entity2.data = "foo2";
+        final long[] ids = mDatabase.intPKeyDao().insertAndGetIds(entity, entity2);
+        assertThat(mDatabase.intPKeyDao().loadDataById(ids), is(Arrays.asList("foo", "foo2")));
+    }
+
+    @Test
+    public void getInsertedIdFromInteger() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.data = "foo";
+        final long id = mDatabase.integerAutoIncPKeyDao().insertAndGetId(entity);
+        assertThat(mDatabase.integerAutoIncPKeyDao().getMe((int) id).data, is("foo"));
+    }
+
+    @Test
+    public void getInsertedIdsFromInteger() {
+        IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
+        entity.data = "foo";
+        IntegerAutoIncPKeyEntity entity2 = new IntegerAutoIncPKeyEntity();
+        entity2.data = "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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/QueryTransactionTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/QueryTransactionTest.java
new file mode 100644
index 0000000..152141d
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/QueryTransactionTest.java
@@ -0,0 +1,460 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+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 androidx.annotation.NonNull;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LiveData;
+import androidx.paging.DataSource;
+import androidx.paging.LivePagedListBuilder;
+import androidx.paging.PagedList;
+import androidx.paging.PositionalDataSource;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.Ignore;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Relation;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.RoomWarnings;
+import androidx.room.Transaction;
+import androidx.room.paging.LimitOffsetDataSource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import io.reactivex.schedulers.Schedulers;
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(Parameterized.class)
+@SuppressWarnings("CheckReturnValue")
+public class QueryTransactionTest {
+    @Rule
+    public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule();
+    private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0);
+    private TransactionDb mDb;
+    private final boolean mUseTransactionDao;
+    private Entity1Dao mDao;
+    private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest
+            .TestLifecycleOwner();
+
+    @NonNull
+    @Parameterized.Parameters(name = "useTransaction_{0}")
+    public static Boolean[] getParams() {
+        return new Boolean[]{false, true};
+    }
+
+    public QueryTransactionTest(boolean useTransactionDao) {
+        mUseTransactionDao = useTransactionDao;
+    }
+
+    @Before
+    public void initDb() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START));
+
+        resetTransactionCount();
+        mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
+                TransactionDb.class).build();
+        mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
+        drain();
+    }
+
+    @After
+    public void closeDb() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY));
+        drain();
+        mDb.close();
+    }
+
+    @Test
+    public void readList() {
+        mDao.insert(new Entity1(1, "foo"));
+        resetTransactionCount();
+
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        List<Entity1> allEntities = mDao.allEntities();
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void liveData() {
+        LiveData<List<Entity1>> listLiveData = mDao.liveData();
+        observeForever(listLiveData);
+        drain();
+        assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList()));
+
+        resetTransactionCount();
+        mDao.insert(new Entity1(1, "foo"));
+        drain();
+
+        //noinspection ConstantConditions
+        assertThat(listLiveData.getValue().size(), is(1));
+        int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+        assertTransactionCount(listLiveData.getValue(), expectedTransactionCount);
+    }
+
+    @Test
+    public void flowable() {
+        Flowable<List<Entity1>> flowable = mDao.flowable();
+        TestSubscriber<List<Entity1>> subscriber = observe(flowable);
+        drain();
+        assertThat(subscriber.values().size(), is(1));
+
+        resetTransactionCount();
+        mDao.insert(new Entity1(1, "foo"));
+        drain();
+
+        List<Entity1> allEntities = subscriber.values().get(1);
+        assertThat(allEntities.size(), is(1));
+        int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void maybe() {
+        mDao.insert(new Entity1(1, "foo"));
+        resetTransactionCount();
+
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        Maybe<List<Entity1>> listMaybe = mDao.maybe();
+        TestObserver<List<Entity1>> observer = observe(listMaybe);
+        drain();
+        List<Entity1> allEntities = observer.values().get(0);
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void single() {
+        mDao.insert(new Entity1(1, "foo"));
+        resetTransactionCount();
+
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        Single<List<Entity1>> listMaybe = mDao.single();
+        TestObserver<List<Entity1>> observer = observe(listMaybe);
+        drain();
+        List<Entity1> allEntities = observer.values().get(0);
+        assertTransactionCount(allEntities, expectedTransactionCount);
+    }
+
+    @Test
+    public void relation() {
+        mDao.insert(new Entity1(1, "foo"));
+        mDao.insert(new Child(1, 1));
+        mDao.insert(new Child(2, 1));
+        resetTransactionCount();
+
+        List<Entity1WithChildren> result = mDao.withRelation();
+        int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
+        assertTransactionCountWithChildren(result, expectedTransactionCount);
+    }
+
+    @Test
+    public void pagedList() {
+        LiveData<PagedList<Entity1>> pagedList =
+                new LivePagedListBuilder<>(mDao.pagedList(), 10).build();
+        observeForever(pagedList);
+        drain();
+        assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
+
+        mDao.insert(new Entity1(1, "foo"));
+        drain();
+        //noinspection ConstantConditions
+        assertThat(pagedList.getValue().size(), is(1));
+        assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 2 : 1);
+
+        mDao.insert(new Entity1(2, "bar"));
+        drain();
+        assertThat(pagedList.getValue().size(), is(2));
+        assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 4 : 2);
+    }
+
+    @Test
+    public void dataSource() {
+        mDao.insert(new Entity1(2, "bar"));
+        drain();
+        resetTransactionCount();
+        @SuppressWarnings("deprecation")
+        LimitOffsetDataSource<Entity1> dataSource =
+                (LimitOffsetDataSource<Entity1>) mDao.dataSource();
+        dataSource.loadRange(0, 10);
+        assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
+    }
+
+    private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) {
+        assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+        assertThat(allEntities.isEmpty(), is(false));
+        for (Entity1 entity1 : allEntities) {
+            assertThat(entity1.transactionId, is(expectedTransactionCount));
+        }
+    }
+
+    private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities,
+            int expectedTransactionCount) {
+        assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
+        assertThat(allEntities.isEmpty(), is(false));
+        for (Entity1WithChildren entity1 : allEntities) {
+            assertThat(entity1.transactionId, is(expectedTransactionCount));
+            assertThat(entity1.children, notNullValue());
+            assertThat(entity1.children.isEmpty(), is(false));
+            for (Child child : entity1.children) {
+                assertThat(child.transactionId, is(expectedTransactionCount));
+            }
+        }
+    }
+
+    private void resetTransactionCount() {
+        sStartedTransactionCount.set(0);
+    }
+
+    private void drain() {
+        try {
+            countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new AssertionError("interrupted", e);
+        } catch (TimeoutException e) {
+            throw new AssertionError("drain timed out", e);
+        }
+    }
+
+    private <T> TestSubscriber<T> observe(final Flowable<T> flowable) {
+        TestSubscriber<T> subscriber = new TestSubscriber<>();
+        flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(subscriber);
+        return subscriber;
+    }
+
+    private <T> TestObserver<T> observe(final Maybe<T> maybe) {
+        TestObserver<T> observer = new TestObserver<>();
+        maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(observer);
+        return observer;
+    }
+
+    private <T> TestObserver<T> observe(final Single<T> single) {
+        TestObserver<T> observer = new TestObserver<>();
+        single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
+                .subscribeWith(observer);
+        return observer;
+    }
+
+    private <T> void observeForever(final LiveData<T> liveData) {
+        FutureTask<Void> futureTask = new FutureTask<>(() -> {
+            liveData.observe(mLifecycleOwner, t -> {
+            });
+            return null;
+        });
+        ArchTaskExecutor.getMainThreadExecutor().execute(futureTask);
+        try {
+            futureTask.get();
+        } catch (InterruptedException e) {
+            throw new AssertionError("interrupted", e);
+        } catch (ExecutionException e) {
+            throw new AssertionError("execution error", e);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    static class Entity1WithChildren extends Entity1 {
+        @Relation(entity = Child.class, parentColumn = "id",
+                entityColumn = "entity1Id")
+        public List<Child> children;
+
+        Entity1WithChildren(int id, String value) {
+            super(id, value);
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity
+    static class Child {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public int entity1Id;
+        @Ignore
+        public final int transactionId = sStartedTransactionCount.get();
+
+        Child(int id, int entity1Id) {
+            this.id = id;
+            this.entity1Id = entity1Id;
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @Entity
+    static class Entity1 {
+        @PrimaryKey(autoGenerate = true)
+        public int id;
+        public String value;
+        @Ignore
+        public final int transactionId = sStartedTransactionCount.get();
+
+        Entity1(int id, String value) {
+            this.id = id;
+            this.value = value;
+        }
+    }
+
+    // we don't support dao inheritance for queries so for now, go with this
+    interface Entity1Dao {
+        String SELECT_ALL = "select * from Entity1";
+
+        List<Entity1> allEntities();
+
+        Flowable<List<Entity1>> flowable();
+
+        Maybe<List<Entity1>> maybe();
+
+        Single<List<Entity1>> single();
+
+        LiveData<List<Entity1>> liveData();
+
+        List<Entity1WithChildren> withRelation();
+
+        DataSource.Factory<Integer, Entity1> pagedList();
+
+        PositionalDataSource<Entity1> dataSource();
+
+        @Insert
+        void insert(Entity1 entity1);
+
+        @Insert
+        void insert(Child entity1);
+    }
+
+    @Dao
+    interface EntityDao extends Entity1Dao {
+        @Override
+        @Query(SELECT_ALL)
+        List<Entity1> allEntities();
+
+        @Override
+        @Query(SELECT_ALL)
+        Flowable<List<Entity1>> flowable();
+
+        @Override
+        @Query(SELECT_ALL)
+        LiveData<List<Entity1>> liveData();
+
+        @Override
+        @Query(SELECT_ALL)
+        Maybe<List<Entity1>> maybe();
+
+        @Override
+        @Query(SELECT_ALL)
+        Single<List<Entity1>> single();
+
+        @Override
+        @Query(SELECT_ALL)
+        @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
+        List<Entity1WithChildren> withRelation();
+
+        @Override
+        @Query(SELECT_ALL)
+        DataSource.Factory<Integer, Entity1> pagedList();
+
+        @Override
+        @Query(SELECT_ALL)
+        PositionalDataSource<Entity1> dataSource();
+    }
+
+    @Dao
+    interface TransactionDao extends Entity1Dao {
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        List<Entity1> allEntities();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        Flowable<List<Entity1>> flowable();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        LiveData<List<Entity1>> liveData();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        Maybe<List<Entity1>> maybe();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        Single<List<Entity1>> single();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        List<Entity1WithChildren> withRelation();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        DataSource.Factory<Integer, Entity1> pagedList();
+
+        @Override
+        @Transaction
+        @Query(SELECT_ALL)
+        PositionalDataSource<Entity1> dataSource();
+    }
+
+    @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false)
+    abstract static class TransactionDb extends RoomDatabase {
+        abstract EntityDao dao();
+
+        abstract TransactionDao transactionDao();
+
+        @Override
+        public void beginTransaction() {
+            super.beginTransaction();
+            sStartedTransactionCount.incrementAndGet();
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RawQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RawQueryTest.java
new file mode 100644
index 0000000..e577c5b
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RawQueryTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Collections.emptyList;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
+import androidx.lifecycle.LiveData;
+import androidx.room.integration.testapp.dao.RawDao;
+import androidx.room.integration.testapp.vo.NameAndLastName;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+import androidx.room.integration.testapp.vo.UserAndPet;
+import androidx.sqlite.db.SimpleSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteQuery;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RawQueryTest extends TestDatabaseTest {
+    @Rule
+    public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+
+    @Test
+    public void entity_null() {
+        User user = mRawDao.getUser(new SimpleSQLiteQuery("SELECT * FROM User WHERE mId = 0"));
+        assertThat(user, is(nullValue()));
+    }
+
+    @Test
+    public void entity_one() {
+        User expected = TestUtil.createUser(3);
+        mUserDao.insert(expected);
+        User received = mRawDao.getUser(new SimpleSQLiteQuery("SELECT * FROM User WHERE mId = ?",
+                new Object[]{3}));
+        assertThat(received, is(expected));
+    }
+
+    @Test
+    public void entity_list() {
+        List<User> expected = TestUtil.createUsersList(1, 2, 3, 4);
+        mUserDao.insertAll(expected.toArray(new User[4]));
+        List<User> received = mRawDao.getUserList(
+                new SimpleSQLiteQuery("SELECT * FROM User ORDER BY mId ASC"));
+        assertThat(received, is(expected));
+    }
+
+    @Test
+    public void entity_liveData_string() throws TimeoutException, InterruptedException {
+        SupportSQLiteQuery query = new SimpleSQLiteQuery(
+                "SELECT * FROM User WHERE mId = ?",
+                new Object[]{3}
+        );
+        liveDataTest(mRawDao.getUserLiveData(query));
+    }
+
+    @Test
+    public void entity_liveData_supportQuery() throws TimeoutException, InterruptedException {
+        liveDataTest(mRawDao.getUserLiveData(
+                new SimpleSQLiteQuery("SELECT * FROM User WHERE mId = ?", new Object[]{3})));
+    }
+
+    private void liveDataTest(
+            final LiveData<User> liveData) throws TimeoutException, InterruptedException {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> liveData.observeForever(user -> { }));
+        drain();
+        assertThat(liveData.getValue(), is(nullValue()));
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        assertThat(liveData.getValue(), is(user));
+        user.setLastName("cxZ");
+        mUserDao.insertOrReplace(user);
+        drain();
+        assertThat(liveData.getValue(), is(user));
+    }
+
+    @Test
+    public void entity_supportSql() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM User WHERE mId = ?",
+                new Object[]{3});
+        User received = mRawDao.getUser(query);
+        assertThat(received, is(user));
+    }
+
+    @Test
+    public void embedded() {
+        User user = TestUtil.createUser(3);
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 1);
+        mUserDao.insert(user);
+        mPetDao.insertAll(pets);
+        UserAndPet received = mRawDao.getUserAndPet(new SimpleSQLiteQuery(
+                "SELECT * FROM User, Pet WHERE User.mId = Pet.mUserId LIMIT 1"));
+        assertThat(received.getUser(), is(user));
+        assertThat(received.getPet(), is(pets[0]));
+    }
+
+    @Test
+    public void relation() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 10);
+        mPetDao.insertAll(pets);
+        UserAndAllPets result = mRawDao
+                .getUserAndAllPets(new SimpleSQLiteQuery("SELECT * FROM User WHERE mId = ?",
+                        new Object[]{3}));
+        assertThat(result.user, is(user));
+        assertThat(result.pets, is(Arrays.asList(pets)));
+    }
+
+    @Test
+    public void pojo() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        NameAndLastName result =
+                mRawDao.getUserNameAndLastName(new SimpleSQLiteQuery("SELECT * FROM User"));
+        assertThat(result, is(new NameAndLastName(user.getName(), user.getLastName())));
+    }
+
+    @Test
+    public void pojo_supportSql() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        NameAndLastName result =
+                mRawDao.getUserNameAndLastNameWithObserved(new SimpleSQLiteQuery(
+                        "SELECT * FROM User WHERE mId = ?",
+                        new Object[]{3}
+                ));
+        assertThat(result, is(new NameAndLastName(user.getName(), user.getLastName())));
+    }
+
+    @Test
+    public void pojo_typeConverter() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        RawDao.UserNameAndBirthday result = mRawDao.getUserAndBirthday(
+                new SimpleSQLiteQuery("SELECT mName, mBirthday FROM user LIMIT 1"));
+        assertThat(result.name, is(user.getName()));
+        assertThat(result.birthday, is(user.getBirthday()));
+    }
+
+    @Test
+    public void embedded_nullField() {
+        User user = TestUtil.createUser(3);
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 1);
+        mUserDao.insert(user);
+        mPetDao.insertAll(pets);
+        UserAndPet received = mRawDao.getUserAndPet(
+                new SimpleSQLiteQuery("SELECT * FROM User LIMIT 1"));
+        assertThat(received.getUser(), is(user));
+        assertThat(received.getPet(), is(nullValue()));
+    }
+
+    @Test
+    public void embedded_list() {
+        User[] users = TestUtil.createUsersArray(3, 5);
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mUserDao.insertAll(users);
+        mPetDao.insertAll(pets);
+        List<UserAndPet> received = mRawDao.getUserAndPetList(
+                new SimpleSQLiteQuery(
+                        "SELECT * FROM User LEFT JOIN Pet ON (User.mId = Pet.mUserId)"
+                        + " ORDER BY mId ASC, mPetId ASC"));
+        assertThat(received.size(), is(3));
+        // row 0
+        assertThat(received.get(0).getUser(), is(users[0]));
+        assertThat(received.get(0).getPet(), is(pets[0]));
+        // row 1
+        assertThat(received.get(1).getUser(), is(users[0]));
+        assertThat(received.get(1).getPet(), is(pets[1]));
+        // row 2
+        assertThat(received.get(2).getUser(), is(users[1]));
+        assertThat(received.get(2).getPet(), is(nullValue()));
+    }
+
+    @Test
+    public void count() {
+        mUserDao.insertAll(TestUtil.createUsersArray(3, 5, 7, 10));
+        int count = mRawDao.count(new SimpleSQLiteQuery("SELECT COUNT(*) FROM User"));
+        assertThat(count, is(4));
+    }
+
+    @Test
+    public void embedded_liveData() throws TimeoutException, InterruptedException {
+        LiveData<List<UserAndPet>> liveData = mRawDao.getUserAndPetListObservable(
+                new SimpleSQLiteQuery("SELECT * FROM User LEFT JOIN Pet ON (User.mId = Pet.mUserId)"
+                        + " ORDER BY mId ASC, mPetId ASC"));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> liveData.observeForever(user -> {
+                })
+        );
+        drain();
+        assertThat(liveData.getValue(), is(emptyList()));
+
+        User[] users = TestUtil.createUsersArray(3, 5);
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+        mUserDao.insertAll(users);
+        drain();
+        List<UserAndPet> justUsers = liveData.getValue();
+        //noinspection ConstantConditions
+        assertThat(justUsers.size(), is(2));
+        assertThat(justUsers.get(0).getUser(), is(users[0]));
+        assertThat(justUsers.get(1).getUser(), is(users[1]));
+        assertThat(justUsers.get(0).getPet(), is(nullValue()));
+        assertThat(justUsers.get(1).getPet(), is(nullValue()));
+
+        mPetDao.insertAll(pets);
+        drain();
+        List<UserAndPet> allItems = liveData.getValue();
+        //noinspection ConstantConditions
+        assertThat(allItems.size(), is(3));
+        // row 0
+        assertThat(allItems.get(0).getUser(), is(users[0]));
+        assertThat(allItems.get(0).getPet(), is(pets[0]));
+        // row 1
+        assertThat(allItems.get(1).getUser(), is(users[0]));
+        assertThat(allItems.get(1).getPet(), is(pets[1]));
+        // row 2
+        assertThat(allItems.get(2).getUser(), is(users[1]));
+        assertThat(allItems.get(2).getPet(), is(nullValue()));
+
+        mDatabase.clearAllTables();
+        drain();
+        assertThat(liveData.getValue(), is(emptyList()));
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    @Test
+    public void relation_liveData() throws TimeoutException, InterruptedException {
+        LiveData<UserAndAllPets> liveData = mRawDao
+                .getUserAndAllPetsObservable(
+                        new SimpleSQLiteQuery("SELECT * FROM User WHERE mId = ?",
+                                new Object[]{3}));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> liveData.observeForever(user -> {
+                })
+        );
+        drain();
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        assertThat(liveData.getValue().user, is(user));
+        assertThat(liveData.getValue().pets, is(emptyList()));
+        Pet[] pets = TestUtil.createPetsForUser(3, 1, 5);
+        mPetDao.insertAll(pets);
+        drain();
+        assertThat(liveData.getValue().user, is(user));
+        assertThat(liveData.getValue().pets, is(Arrays.asList(pets)));
+    }
+
+    private void drain() throws TimeoutException, InterruptedException {
+        mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RelationWithReservedKeywordTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RelationWithReservedKeywordTest.java
new file mode 100644
index 0000000..774b879
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RelationWithReservedKeywordTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Collections.singletonList;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Embedded;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Relation;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.Transaction;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RelationWithReservedKeywordTest {
+    private MyDatabase mDb;
+
+    @Before
+    public void initDb() {
+        mDb = Room.inMemoryDatabaseBuilder(
+                InstrumentationRegistry.getTargetContext(),
+                MyDatabase.class).build();
+    }
+
+    @Test
+    public void loadRelation() {
+        Category category = new Category(1, "cat1");
+        mDb.getDao().insert(category);
+        Topic topic = new Topic(2, 1, "foo");
+        mDb.getDao().insert(topic);
+        List<CategoryWithTopics> categoryWithTopics = mDb.getDao().loadAll();
+        assertThat(categoryWithTopics.size(), is(1));
+        assertThat(categoryWithTopics.get(0).category, is(category));
+        assertThat(categoryWithTopics.get(0).topics, is(singletonList(topic)));
+    }
+
+    @Entity(tableName = "categories")
+    static class Category {
+
+        @PrimaryKey(autoGenerate = true)
+        public final long id;
+
+        public final String name;
+
+        Category(long id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Category category = (Category) o;
+            //noinspection SimplifiableIfStatement
+            if (id != category.id) return false;
+            return name != null ? name.equals(category.name) : category.name == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = (int) (id ^ (id >>> 32));
+            result = 31 * result + (name != null ? name.hashCode() : 0);
+            return result;
+        }
+    }
+
+    @Dao
+    interface MyDao {
+        @Transaction
+        @Query("SELECT * FROM categories")
+        List<CategoryWithTopics> loadAll();
+
+        @Insert
+        void insert(Category... categories);
+
+        @Insert
+        void insert(Topic... topics);
+    }
+
+    @Database(
+            entities = {Category.class, Topic.class},
+            version = 1,
+            exportSchema = false)
+    abstract static class MyDatabase extends RoomDatabase {
+        abstract MyDao getDao();
+    }
+
+
+    @SuppressWarnings("WeakerAccess")
+    static class CategoryWithTopics {
+        @Embedded
+        public Category category;
+
+        @Relation(
+                parentColumn = "id",
+                entityColumn = "category_id",
+                entity = Topic.class)
+        public List<Topic> topics;
+    }
+
+    @Entity(
+            tableName = "topics",
+            foreignKeys = @ForeignKey(
+                    entity = Category.class,
+                    parentColumns = "id",
+                    childColumns = "category_id",
+                    onDelete = ForeignKey.CASCADE),
+            indices = @Index("category_id"))
+    static class Topic {
+
+        @PrimaryKey(autoGenerate = true)
+        public final long id;
+
+        @ColumnInfo(name = "category_id")
+        public final long categoryId;
+
+        public final String to;
+
+        Topic(long id, long categoryId, String to) {
+            this.id = id;
+            this.categoryId = categoryId;
+            this.to = to;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Topic topic = (Topic) o;
+            if (id != topic.id) return false;
+            //noinspection SimplifiableIfStatement
+            if (categoryId != topic.categoryId) return false;
+            return to != null ? to.equals(topic.to) : topic.to == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = (int) (id ^ (id >>> 32));
+            result = 31 * result + (int) (categoryId ^ (categoryId >>> 32));
+            result = 31 * result + (to != null ? to.hashCode() : 0);
+            return result;
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RxJava2Test.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RxJava2Test.java
new file mode 100644
index 0000000..9878cd2
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RxJava2Test.java
@@ -0,0 +1,332 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
+import androidx.room.EmptyResultSetException;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+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;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RxJava2Test extends TestDatabaseTest {
+
+    private TestScheduler mTestScheduler;
+
+    @Before
+    public void setupSchedulers() {
+        mTestScheduler = new TestScheduler();
+        mTestScheduler.start();
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+            @Override
+            public void executeOnDiskIO(Runnable runnable) {
+                mTestScheduler.scheduleDirect(runnable);
+            }
+
+            @Override
+            public void postToMainThread(Runnable runnable) {
+                Assert.fail("no main thread in this test");
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return false;
+            }
+        });
+    }
+
+    @After
+    public void clearSchedulers() {
+        mTestScheduler.shutdown();
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    private void drain() throws InterruptedException {
+        mTestScheduler.triggerActions();
+    }
+
+    @Test
+    public void maybeUser_Empty() throws InterruptedException {
+        TestObserver<User> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.maybeUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        testObserver.assertComplete();
+        testObserver.assertNoValues();
+        disposable.dispose();
+    }
+
+    @Test
+    public void maybeUser_WithData() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        TestObserver<User> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.maybeUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        testObserver.assertComplete();
+        testObserver.assertValue(user);
+
+        disposable.dispose();
+    }
+
+    @Test
+    public void maybeUsers_EmptyList() throws InterruptedException {
+        TestObserver<List<User>> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.maybeUsersByIds(3, 5, 7).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        testObserver.assertComplete();
+        testObserver.assertValue(Collections.<User>emptyList());
+        disposable.dispose();
+    }
+
+    @Test
+    public void maybeUsers_WithValue() throws InterruptedException {
+        User[] users = TestUtil.createUsersArray(3, 5);
+        mUserDao.insertAll(users);
+        TestObserver<List<User>> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.maybeUsersByIds(3, 5, 7).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        testObserver.assertComplete();
+        // since this is a clean db, it is ok to rely on the order for the test.
+        testObserver.assertValue(Arrays.asList(users));
+        disposable.dispose();
+    }
+
+    @Test
+    public void singleUser_Empty() throws InterruptedException {
+        TestObserver<User> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.singleUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        // figure out which error we should dispatch
+        testObserver.assertError(EmptyResultSetException.class);
+        testObserver.assertNoValues();
+        disposable.dispose();
+    }
+
+    @Test
+    public void singleUser_WithData() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        TestObserver<User> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.singleUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        testObserver.assertComplete();
+        testObserver.assertValue(user);
+
+        disposable.dispose();
+    }
+
+    @Test
+    public void singleUsers_EmptyList() throws InterruptedException {
+        TestObserver<List<User>> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.singleUsersByIds(3, 5, 7).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        testObserver.assertComplete();
+        testObserver.assertValue(Collections.<User>emptyList());
+        disposable.dispose();
+    }
+
+    @Test
+    public void singleUsers_WithValue() throws InterruptedException {
+        User[] users = TestUtil.createUsersArray(3, 5);
+        mUserDao.insertAll(users);
+        TestObserver<List<User>> testObserver = new TestObserver<>();
+        Disposable disposable = mUserDao.singleUsersByIds(3, 5, 7).observeOn(mTestScheduler)
+                .subscribeWith(testObserver);
+        drain();
+        testObserver.assertComplete();
+        // since this is a clean db, it is ok to rely on the order for the test.
+        testObserver.assertValue(Arrays.asList(users));
+        disposable.dispose();
+    }
+
+    @Test
+    public void observeOnce() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        TestSubscriber<User> consumer = new TestSubscriber<>();
+        Disposable disposable = mUserDao.flowableUserById(3).subscribeWith(consumer);
+        drain();
+        consumer.assertValue(user);
+        disposable.dispose();
+    }
+
+    @Test
+    public void observeChangeAndDispose() throws InterruptedException {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        TestSubscriber<User> consumer = new TestSubscriber<>();
+        Disposable disposable = mUserDao.flowableUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(consumer);
+        drain();
+        assertThat(consumer.values().get(0), is(user));
+        user.setName("rxy");
+        mUserDao.insertOrReplace(user);
+        drain();
+        User next = consumer.values().get(1);
+        assertThat(next, is(user));
+        disposable.dispose();
+        user.setName("foo");
+        mUserDao.insertOrReplace(user);
+        drain();
+        assertThat(consumer.valueCount(), is(2));
+    }
+
+    @Test
+    @MediumTest
+    public void observeEmpty() throws InterruptedException {
+        TestSubscriber<User> consumer = new TestSubscriber<>();
+        Disposable disposable = mUserDao.flowableUserById(3).observeOn(mTestScheduler)
+                .subscribeWith(consumer);
+        drain();
+        consumer.assertNoValues();
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        drain();
+        assertThat(consumer.values().get(0), is(user));
+        disposable.dispose();
+        user.setAge(88);
+        mUserDao.insertOrReplace(user);
+        drain();
+        assertThat(consumer.valueCount(), is(1));
+    }
+
+    @Test
+    public void flowableCountUsers() throws InterruptedException {
+        TestSubscriber<Integer> consumer = new TestSubscriber<>();
+        mUserDao.flowableCountUsers()
+                .observeOn(mTestScheduler)
+                .subscribe(consumer);
+        drain();
+        assertThat(consumer.values().get(0), is(0));
+        mUserDao.insertAll(TestUtil.createUsersArray(1, 3, 4, 6));
+        drain();
+        assertThat(consumer.values().get(1), is(4));
+        mUserDao.deleteByUids(3, 7);
+        drain();
+        assertThat(consumer.values().get(2), is(3));
+        mUserDao.deleteByUids(101);
+        drain();
+        assertThat(consumer.valueCount(), is(3));
+    }
+
+    @Test
+    @MediumTest
+    public void publisherCountUsers() throws InterruptedException {
+        TestSubscriber<Integer> subscriber = new TestSubscriber<>();
+        mUserDao.publisherCountUsers().subscribe(subscriber);
+        drain();
+        subscriber.assertSubscribed();
+        subscriber.request(2);
+        drain();
+        subscriber.assertValue(0);
+        mUserDao.insert(TestUtil.createUser(2));
+        drain();
+        subscriber.assertValues(0, 1);
+        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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
new file mode 100644
index 0000000..992d8f8
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
+import androidx.room.Room;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.vo.User;
+
+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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
new file mode 100644
index 0000000..0207190
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.Room;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.BlobEntityDao;
+import androidx.room.integration.testapp.dao.PetDao;
+import androidx.room.integration.testapp.dao.ProductDao;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.dao.UserPetDao;
+import androidx.room.integration.testapp.vo.BlobEntity;
+import androidx.room.integration.testapp.vo.Day;
+import androidx.room.integration.testapp.vo.NameAndLastName;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.Product;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.integration.testapp.vo.UserAndAllPets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SimpleEntityReadWriteTest {
+    private UserDao mUserDao;
+    private BlobEntityDao mBlobEntityDao;
+    private PetDao mPetDao;
+    private UserPetDao mUserPetDao;
+    private ProductDao mProductDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = db.getUserDao();
+        mPetDao = db.getPetDao();
+        mUserPetDao = db.getUserPetDao();
+        mBlobEntityDao = db.getBlobEntityDao();
+        mProductDao = db.getProductDao();
+    }
+
+    @Test
+    public void writeUserAndReadInList() throws Exception {
+        User user = TestUtil.createUser(3);
+        user.setName("george");
+        mUserDao.insert(user);
+        List<User> byName = mUserDao.findUsersByName("george");
+        assertThat(byName.get(0), equalTo(user));
+    }
+
+    @Test
+    public void insertNull() throws Exception {
+        @SuppressWarnings("ConstantConditions")
+        Product product = new Product(1, null);
+        Throwable throwable = null;
+        try {
+            mProductDao.insert(product);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertNotNull("Was expecting an exception", throwable);
+        assertThat(throwable, instanceOf(SQLiteConstraintException.class));
+    }
+
+    @Test
+    public void insertDifferentEntities() throws Exception {
+        User user1 = TestUtil.createUser(3);
+        user1.setName("george");
+        Pet pet = TestUtil.createPet(1);
+        pet.setUserId(3);
+        pet.setName("a");
+        mUserPetDao.insertUserAndPet(user1, pet);
+        assertThat(mUserDao.count(), is(1));
+        List<UserAndAllPets> inserted = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(inserted, hasSize(1));
+        assertThat(inserted.get(0).user.getId(), is(3));
+        assertThat(inserted.get(0).user.getName(), is(equalTo("george")));
+        assertThat(inserted.get(0).pets, hasSize(1));
+        assertThat(inserted.get(0).pets.get(0).getPetId(), is(1));
+        assertThat(inserted.get(0).pets.get(0).getName(), is("a"));
+        assertThat(inserted.get(0).pets.get(0).getUserId(), is(3));
+        pet.setName("b");
+        mUserPetDao.updateUsersAndPets(new User[]{user1}, new Pet[]{pet});
+        List<UserAndAllPets> updated = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(updated, hasSize(1));
+        assertThat(updated.get(0).pets, hasSize(1));
+        assertThat(updated.get(0).pets.get(0).getName(), is("b"));
+        User user2 = TestUtil.createUser(5);
+        user2.setName("chet");
+        mUserDao.insert(user2);
+        assertThat(mUserDao.count(), is(2));
+        mUserPetDao.delete2UsersAndPets(user1, user2, new Pet[]{pet});
+        List<UserAndAllPets> deleted = mUserPetDao.loadAllUsersWithTheirPets();
+        assertThat(deleted, hasSize(0));
+    }
+
+    @Test
+    public void insertDifferentEntities_transaction() throws Exception {
+        Pet pet = TestUtil.createPet(1);
+        mPetDao.insertOrReplace(pet);
+        assertThat(mPetDao.count(), is(1));
+        User user = TestUtil.createUser(3);
+        try {
+            mUserPetDao.insertUserAndPet(user, pet);
+            fail("Exception expected");
+        } catch (SQLiteConstraintException ignored) {
+        }
+        assertThat(mUserDao.count(), is(0));
+        assertThat(mPetDao.count(), is(1));
+    }
+
+    @Test
+    public void throwExceptionOnConflict() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+
+        User user2 = TestUtil.createUser(3);
+        try {
+            mUserDao.insert(user2);
+            throw new AssertionError("didn't throw in conflicting insertion");
+        } catch (SQLiteException ignored) {
+        }
+    }
+
+    @Test
+    public void replaceOnConflict() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+
+        User user2 = TestUtil.createUser(3);
+        mUserDao.insertOrReplace(user2);
+
+        assertThat(mUserDao.load(3), equalTo(user2));
+        assertThat(mUserDao.load(3), not(equalTo(user)));
+    }
+
+    @Test
+    public void updateSimple() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        user.setName("i am an updated name");
+        assertThat(mUserDao.update(user), is(1));
+        assertThat(mUserDao.load(user.getId()), equalTo(user));
+    }
+
+    @Test
+    public void updateNonExisting() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        User user2 = TestUtil.createUser(4);
+        assertThat(mUserDao.update(user2), is(0));
+    }
+
+    @Test
+    public void updateList() {
+        List<User> users = TestUtil.createUsersList(3, 4, 5);
+        mUserDao.insertAll(users.toArray(new User[3]));
+        for (User user : users) {
+            user.setName("name " + user.getId());
+        }
+        assertThat(mUserDao.updateAll(users), is(3));
+        for (User user : users) {
+            assertThat(mUserDao.load(user.getId()).getName(), is("name " + user.getId()));
+        }
+    }
+
+    @Test
+    public void updateListPartial() {
+        List<User> existingUsers = TestUtil.createUsersList(3, 4, 5);
+        mUserDao.insertAll(existingUsers.toArray(new User[3]));
+        for (User user : existingUsers) {
+            user.setName("name " + user.getId());
+        }
+        List<User> allUsers = TestUtil.createUsersList(7, 8, 9);
+        allUsers.addAll(existingUsers);
+        assertThat(mUserDao.updateAll(allUsers), is(3));
+        for (User user : existingUsers) {
+            assertThat(mUserDao.load(user.getId()).getName(), is("name " + user.getId()));
+        }
+    }
+
+    @Test
+    public void delete() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        assertThat(mUserDao.delete(user), is(1));
+        assertThat(mUserDao.delete(user), is(0));
+        assertThat(mUserDao.load(3), is(nullValue()));
+    }
+
+    @Test
+    public void deleteAll() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
+        mUserDao.insertAll(users);
+        // there is actually no guarantee for this order by works fine since they are ordered for
+        // the test and it is a new database (no pages to recycle etc)
+        assertThat(mUserDao.loadByIds(3, 5, 7, 9), is(users));
+        int deleteCount = mUserDao.deleteAll(new User[]{users[0], users[3],
+                TestUtil.createUser(9)});
+        assertThat(deleteCount, is(2));
+        assertThat(mUserDao.loadByIds(3, 5, 7, 9), is(new User[]{users[1], users[2]}));
+    }
+
+    @Test
+    public void deleteEverything() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        assertThat(mUserDao.count(), is(1));
+        int count = mUserDao.deleteEverything();
+        assertThat(count, is(1));
+        assertThat(mUserDao.count(), is(0));
+    }
+
+    @Test
+    public void findByBoolean() {
+        User user1 = TestUtil.createUser(3);
+        user1.setAdmin(true);
+        User user2 = TestUtil.createUser(5);
+        user2.setAdmin(false);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.findByAdmin(true), is(Arrays.asList(user1)));
+        assertThat(mUserDao.findByAdmin(false), is(Arrays.asList(user2)));
+    }
+
+    @Test
+    public void returnBoolean() {
+        User user1 = TestUtil.createUser(1);
+        User user2 = TestUtil.createUser(2);
+        user1.setAdmin(true);
+        user2.setAdmin(false);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.isAdmin(1), is(true));
+        assertThat(mUserDao.isAdmin(2), is(false));
+    }
+
+    @Test
+    public void findByCollateNoCase() {
+        User user = TestUtil.createUser(3);
+        user.setCustomField("abc");
+        mUserDao.insert(user);
+        List<User> users = mUserDao.findByCustomField("ABC");
+        assertThat(users, hasSize(1));
+        assertThat(users.get(0).getId(), is(3));
+    }
+
+    @Test
+    public void deleteByAge() {
+        User user1 = TestUtil.createUser(3);
+        user1.setAge(30);
+        User user2 = TestUtil.createUser(5);
+        user2.setAge(45);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.deleteAgeGreaterThan(60), is(0));
+        assertThat(mUserDao.deleteAgeGreaterThan(45), is(0));
+        assertThat(mUserDao.deleteAgeGreaterThan(35), is(1));
+        assertThat(mUserDao.loadByIds(3, 5), is(new User[]{user1}));
+    }
+
+    @Test
+    public void deleteByAgeRange() {
+        User user1 = TestUtil.createUser(3);
+        user1.setAge(30);
+        User user2 = TestUtil.createUser(5);
+        user2.setAge(45);
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        assertThat(mUserDao.deleteByAgeRange(35, 40), is(0));
+        assertThat(mUserDao.deleteByAgeRange(25, 30), is(1));
+        assertThat(mUserDao.loadByIds(3, 5), is(new User[]{user2}));
+    }
+
+    @Test
+    public void deleteByUIds() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9, 11);
+        mUserDao.insertAll(users);
+        assertThat(mUserDao.deleteByUids(2, 4, 6), is(0));
+        assertThat(mUserDao.deleteByUids(3, 11), is(2));
+        assertThat(mUserDao.loadByIds(3, 5, 7, 9, 11), is(new User[]{
+                users[1], users[2], users[3]
+        }));
+    }
+
+    @Test
+    public void updateNameById() {
+        User[] usersArray = TestUtil.createUsersArray(3, 5, 7);
+        mUserDao.insertAll(usersArray);
+        assertThat("test sanity", usersArray[1].getName(), not(equalTo("updated name")));
+        int changed = mUserDao.updateById(5, "updated name");
+        assertThat(changed, is(1));
+        assertThat(mUserDao.load(5).getName(), is("updated name"));
+    }
+
+    @Test
+    public void incrementIds() {
+        User[] usersArr = TestUtil.createUsersArray(2, 4, 6);
+        mUserDao.insertAll(usersArr);
+        mUserDao.incrementIds(1);
+        assertThat(mUserDao.loadIds(), is(Arrays.asList(3, 5, 7)));
+    }
+
+    @Test
+    public void incrementAgeOfAll() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7);
+        users[0].setAge(3);
+        users[1].setAge(5);
+        users[2].setAge(7);
+        mUserDao.insertAll(users);
+        assertThat(mUserDao.count(), is(3));
+        mUserDao.incrementAgeOfAll();
+        for (User user : mUserDao.loadByIds(3, 5, 7)) {
+            assertThat(user.getAge(), is(user.getId() + 1));
+        }
+    }
+
+    @Test
+    public void findByIntQueryParameter() {
+        User user = TestUtil.createUser(1);
+        final String name = "my name";
+        user.setName(name);
+        mUserDao.insert(user);
+        assertThat(mUserDao.findByNameLength(name.length()), is(Collections.singletonList(user)));
+    }
+
+    @Test
+    public void findByIntFieldMatch() {
+        User user = TestUtil.createUser(1);
+        user.setAge(19);
+        mUserDao.insert(user);
+        assertThat(mUserDao.findByAge(19), is(Collections.singletonList(user)));
+    }
+
+    @Test
+    public void customConverterField() {
+        User user = TestUtil.createUser(20);
+        Date theDate = new Date(System.currentTimeMillis() - 200);
+        user.setBirthday(theDate);
+        mUserDao.insert(user);
+        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime() - 100),
+                new Date(theDate.getTime() + 1)).get(0), is(user));
+        assertThat(mUserDao.findByBirthdayRange(new Date(theDate.getTime()),
+                new Date(theDate.getTime() + 1)).size(), is(0));
+    }
+
+    @Test
+    public void renamedField() {
+        User user = TestUtil.createUser(3);
+        user.setCustomField("foo laaa");
+        mUserDao.insertOrReplace(user);
+        User loaded = mUserDao.load(3);
+        assertThat(loaded.getCustomField(), is("foo laaa"));
+        assertThat(loaded, is(user));
+    }
+
+    @Test
+    public void readViaCursor() {
+        User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
+        mUserDao.insertAll(users);
+        Cursor cursor = mUserDao.findUsersAsCursor(3, 5, 9);
+        try {
+            assertThat(cursor.getCount(), is(3));
+            assertThat(cursor.moveToNext(), is(true));
+            assertThat(cursor.getInt(0), is(3));
+            assertThat(cursor.moveToNext(), is(true));
+            assertThat(cursor.getInt(0), is(5));
+            assertThat(cursor.moveToNext(), is(true));
+            assertThat(cursor.getInt(0), is(9));
+            assertThat(cursor.moveToNext(), is(false));
+        } finally {
+            cursor.close();
+        }
+    }
+
+    @Test
+    public void readDirectWithTypeAdapter() {
+        User user = TestUtil.createUser(3);
+        user.setBirthday(null);
+        mUserDao.insert(user);
+        assertThat(mUserDao.getBirthday(3), is(nullValue()));
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.YEAR, 3);
+        Date birthday = calendar.getTime();
+        user.setBirthday(birthday);
+
+        mUserDao.update(user);
+        assertThat(mUserDao.getBirthday(3), is(birthday));
+    }
+
+    @Test
+    public void emptyInQuery() {
+        User[] users = mUserDao.loadByIds();
+        assertThat(users, is(new User[0]));
+    }
+
+    @Test
+    public void blob() {
+        BlobEntity a = new BlobEntity(1, "abc".getBytes());
+        BlobEntity b = new BlobEntity(2, "def".getBytes());
+        mBlobEntityDao.insert(a);
+        mBlobEntityDao.insert(b);
+        List<BlobEntity> list = mBlobEntityDao.selectAll();
+        assertThat(list, hasSize(2));
+        mBlobEntityDao.updateContent(2, "ghi".getBytes());
+        assertThat(mBlobEntityDao.getContent(2), is(equalTo("ghi".getBytes())));
+    }
+
+    @Test
+    public void transactionByRunnable() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(5);
+        mUserDao.insertBothByRunnable(a, b);
+        assertThat(mUserDao.count(), is(2));
+    }
+
+    @Test
+    public void transactionByRunnable_failure() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(3);
+        boolean caught = false;
+        try {
+            mUserDao.insertBothByRunnable(a, b);
+        } catch (SQLiteConstraintException e) {
+            caught = true;
+        }
+        assertTrue("SQLiteConstraintException expected", caught);
+        assertThat(mUserDao.count(), is(0));
+    }
+
+    @Test
+    public void transactionByCallable() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(5);
+        int count = mUserDao.insertBothByCallable(a, b);
+        assertThat(mUserDao.count(), is(2));
+        assertThat(count, is(2));
+    }
+
+    @Test
+    public void transactionByCallable_failure() {
+        User a = TestUtil.createUser(3);
+        User b = TestUtil.createUser(3);
+        boolean caught = false;
+        try {
+            mUserDao.insertBothByCallable(a, b);
+        } catch (SQLiteConstraintException e) {
+            caught = true;
+        }
+        assertTrue("SQLiteConstraintException expected", caught);
+        assertThat(mUserDao.count(), is(0));
+    }
+
+    @Test
+    public void transactionByDefaultImplementation() {
+        Pet pet1 = TestUtil.createPet(1);
+        mPetDao.insertOrReplace(pet1);
+        assertThat(mPetDao.count(), is(1));
+        assertThat(mPetDao.allIds()[0], is(1));
+        Pet pet2 = TestUtil.createPet(2);
+        mPetDao.deleteAndInsert(pet1, pet2, false);
+        assertThat(mPetDao.count(), is(1));
+        assertThat(mPetDao.allIds()[0], is(2));
+    }
+
+    @Test
+    public void transactionByDefaultImplementation_failure() {
+        Pet pet1 = TestUtil.createPet(1);
+        mPetDao.insertOrReplace(pet1);
+        assertThat(mPetDao.count(), is(1));
+        assertThat(mPetDao.allIds()[0], is(1));
+        Pet pet2 = TestUtil.createPet(2);
+        Throwable throwable = null;
+        try {
+            mPetDao.deleteAndInsert(pet1, pet2, true);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertNotNull("Was expecting an exception", throwable);
+        assertThat(mPetDao.count(), is(1));
+        assertThat(mPetDao.allIds()[0], is(1));
+    }
+
+    @Test
+    public void multipleInParamsFollowedByASingleParam_delete() {
+        User user = TestUtil.createUser(3);
+        user.setAge(30);
+        mUserDao.insert(user);
+        assertThat(mUserDao.deleteByAgeAndIds(20, Arrays.asList(3, 5)), is(0));
+        assertThat(mUserDao.count(), is(1));
+        assertThat(mUserDao.deleteByAgeAndIds(30, Arrays.asList(3, 5)), is(1));
+        assertThat(mUserDao.count(), is(0));
+    }
+
+    @Test
+    public void multipleInParamsFollowedByASingleParam_update() {
+        User user = TestUtil.createUser(3);
+        user.setAge(30);
+        user.setWeight(10f);
+        mUserDao.insert(user);
+        assertThat(mUserDao.updateByAgeAndIds(3f, 20, Arrays.asList(3, 5)), is(0));
+        assertThat(mUserDao.loadByIds(3)[0].getWeight(), is(10f));
+        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));
+    }
+
+    @Test
+    public void tablePrefix_simpleSelect() {
+        User user = TestUtil.createUser(3);
+        mUserDao.insert(user);
+        NameAndLastName result = mUserDao.getNameAndLastName(3);
+        assertThat(result.getName(), is(user.getName()));
+        assertThat(result.getLastName(), is(user.getLastName()));
+    }
+
+    @Test
+    public void enumSet_simpleLoad() {
+        User a = TestUtil.createUser(3);
+        Set<Day> expected = toSet(Day.MONDAY, Day.TUESDAY);
+        a.setWorkDays(expected);
+        mUserDao.insert(a);
+        User loaded = mUserDao.load(3);
+        assertThat(loaded.getWorkDays(), is(expected));
+    }
+
+    @Test
+    public void enumSet_query() {
+        User user1 = TestUtil.createUser(3);
+        user1.setWorkDays(toSet(Day.MONDAY, Day.FRIDAY));
+        User user2 = TestUtil.createUser(5);
+        user2.setWorkDays(toSet(Day.MONDAY, Day.THURSDAY));
+        mUserDao.insert(user1);
+        mUserDao.insert(user2);
+        List<User> empty = mUserDao.findUsersByWorkDays(toSet(Day.WEDNESDAY));
+        assertThat(empty.size(), is(0));
+        List<User> friday = mUserDao.findUsersByWorkDays(toSet(Day.FRIDAY));
+        assertThat(friday, is(Arrays.asList(user1)));
+        List<User> monday = mUserDao.findUsersByWorkDays(toSet(Day.MONDAY));
+        assertThat(monday, is(Arrays.asList(user1, user2)));
+
+    }
+
+    private Set<Day> toSet(Day... days) {
+        return new HashSet<>(Arrays.asList(days));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestDatabaseTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestDatabaseTest.java
new file mode 100644
index 0000000..56a0cbf
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestDatabaseTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+
+import androidx.room.Room;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.FunnyNamedDao;
+import androidx.room.integration.testapp.dao.PetCoupleDao;
+import androidx.room.integration.testapp.dao.PetDao;
+import androidx.room.integration.testapp.dao.RawDao;
+import androidx.room.integration.testapp.dao.SchoolDao;
+import androidx.room.integration.testapp.dao.SpecificDogDao;
+import androidx.room.integration.testapp.dao.ToyDao;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.dao.UserPetDao;
+import androidx.room.integration.testapp.dao.WithClauseDao;
+
+import org.junit.Before;
+
+@SuppressWarnings("WeakerAccess")
+public abstract class TestDatabaseTest {
+    protected TestDatabase mDatabase;
+    protected UserDao mUserDao;
+    protected PetDao mPetDao;
+    protected UserPetDao mUserPetDao;
+    protected SchoolDao mSchoolDao;
+    protected PetCoupleDao mPetCoupleDao;
+    protected ToyDao mToyDao;
+    protected SpecificDogDao mSpecificDogDao;
+    protected WithClauseDao mWithClauseDao;
+    protected FunnyNamedDao mFunnyNamedDao;
+    protected RawDao mRawDao;
+
+    @Before
+    public void createDb() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabase = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        mUserDao = mDatabase.getUserDao();
+        mPetDao = mDatabase.getPetDao();
+        mUserPetDao = mDatabase.getUserPetDao();
+        mSchoolDao = mDatabase.getSchoolDao();
+        mPetCoupleDao = mDatabase.getPetCoupleDao();
+        mToyDao = mDatabase.getToyDao();
+        mSpecificDogDao = mDatabase.getSpecificDogDao();
+        mWithClauseDao = mDatabase.getWithClauseDao();
+        mFunnyNamedDao = mDatabase.getFunnyNamedDao();
+        mRawDao = mDatabase.getRawDao();
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestUtil.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestUtil.java
new file mode 100644
index 0000000..b3d5e24
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestUtil.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.test;
+
+import androidx.room.integration.testapp.vo.Address;
+import androidx.room.integration.testapp.vo.Coordinates;
+import androidx.room.integration.testapp.vo.Pet;
+import androidx.room.integration.testapp.vo.School;
+import androidx.room.integration.testapp.vo.Toy;
+import androidx.room.integration.testapp.vo.User;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+public class TestUtil {
+    public static User[] createUsersArray(int... ids) {
+        User[] result = new User[ids.length];
+        for (int i = 0; i < ids.length; i++) {
+            result[i] = createUser(ids[i]);
+        }
+        return result;
+    }
+
+    public static List<User> createUsersList(int... ids) {
+        List<User> result = new ArrayList<>();
+        for (int id : ids) {
+            result.add(createUser(id));
+        }
+        return result;
+    }
+
+    public static User createUser(int id) {
+        User user = new User();
+        user.setId(id);
+        user.setName(UUID.randomUUID().toString());
+        user.setLastName(UUID.randomUUID().toString());
+        user.setAge((int) (10 + Math.random() * 50));
+        user.setCustomField(UUID.randomUUID().toString());
+        user.setBirthday(new Date());
+        return user;
+    }
+
+    public static Pet createPet(int id) {
+        Pet pet = new Pet();
+        pet.setPetId(id);
+        pet.setName(UUID.randomUUID().toString());
+        pet.setAdoptionDate(new Date());
+        return pet;
+    }
+
+    public static Toy createToyForPet(Pet pet, int toyId) {
+        Toy toy = new Toy();
+        toy.setName("toy " + toyId);
+        toy.setId(toyId);
+        toy.setPetId(pet.getPetId());
+        return toy;
+    }
+
+    public static Pet[] createPetsForUser(int uid, int petStartId, int count) {
+        Pet[] pets = new Pet[count];
+        for (int i = 0; i < count; i++) {
+            Pet pet = createPet(petStartId++);
+            pet.setUserId(uid);
+            pets[i] = pet;
+        }
+        return pets;
+    }
+
+    public static School createSchool(int id, int managerId) {
+        School school = new School();
+        school.setId(id);
+        school.setName(UUID.randomUUID().toString());
+        school.setManager(createUser(managerId));
+        school.setAddress(createAddress());
+        return school;
+    }
+
+    private static Address createAddress() {
+        Address address = new Address();
+        address.setCoordinates(createCoordinates());
+        address.setPostCode((int) (Math.random() * 1000 + 1000));
+        address.setState(UUID.randomUUID().toString().substring(0, 2));
+        address.setStreet(UUID.randomUUID().toString());
+        return address;
+    }
+
+    private static Coordinates createCoordinates() {
+        Coordinates coordinates = new Coordinates();
+        coordinates.lat = Math.random();
+        coordinates.lng = Math.random();
+        return coordinates;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WithClauseTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WithClauseTest.java
new file mode 100644
index 0000000..85f7697
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WithClauseTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.room.integration.testapp.vo.User;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+public class WithClauseTest extends TestDatabaseTest{
+    @Test
+    public void noSourceOfData() {
+        @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+        List<Integer> expected = Arrays.asList(1);
+        List<Integer> actual = mWithClauseDao.getFactorials(0);
+        assertThat(expected, is(actual));
+
+        expected = Arrays.asList(1, 1, 2, 6, 24);
+        actual = mWithClauseDao.getFactorials(4);
+        assertThat(expected, is(actual));
+    }
+
+    @Test
+    public void sourceOfData() {
+        List<String> expected = new ArrayList<>();
+        List<String> actual = mWithClauseDao.getUsersWithFactorialIds(0);
+        assertThat(expected, is(actual));
+
+        User user = new User();
+        user.setId(0);
+        user.setName("Zero");
+        mUserDao.insert(user);
+        actual = mWithClauseDao.getUsersWithFactorialIds(0);
+        assertThat(expected, is(actual));
+
+        user = new User();
+        user.setId(1);
+        user.setName("One");
+        mUserDao.insert(user);
+        expected.add("One");
+        actual = mWithClauseDao.getUsersWithFactorialIds(0);
+        assertThat(expected, is(actual));
+
+        user = new User();
+        user.setId(6);
+        user.setName("Six");
+        mUserDao.insert(user);
+        actual = mWithClauseDao.getUsersWithFactorialIds(0);
+        assertThat(expected, is(actual));
+
+        actual = mWithClauseDao.getUsersWithFactorialIds(3);
+        expected.add("Six");
+        assertThat(expected, is(actual));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
new file mode 100644
index 0000000..c41d5b2
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2018 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 androidx.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
+import static org.hamcrest.Matchers.hasSize;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.room.InvalidationTracker;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.vo.User;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@SdkSuppress(minSdkVersion = 16)
+public class WriteAheadLoggingTest {
+
+    private static final String DATABASE_NAME = "wal.db";
+    private TestDatabase mDatabase;
+
+    @Before
+    public void openDatabase() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.deleteDatabase(DATABASE_NAME);
+        mDatabase = Room.databaseBuilder(context, TestDatabase.class, DATABASE_NAME)
+                .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
+                .build();
+    }
+
+    @After
+    public void closeDatabase() {
+        mDatabase.close();
+        Context context = InstrumentationRegistry.getTargetContext();
+        context.deleteDatabase(DATABASE_NAME);
+    }
+
+    @Test
+    public void checkJournalMode() {
+        Cursor c = null;
+        try {
+            SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
+            c = db.query("PRAGMA journal_mode");
+            c.moveToFirst();
+            String journalMode = c.getString(0);
+            assertThat(journalMode, is(equalToIgnoringCase("wal")));
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    @Test
+    public void disableWal() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabase.close();
+        mDatabase = Room.databaseBuilder(context, TestDatabase.class, DATABASE_NAME)
+                .setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
+                .build();
+        Cursor c = null;
+        try {
+            SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
+            c = db.query("PRAGMA journal_mode");
+            c.moveToFirst();
+            String journalMode = c.getString(0);
+            assertThat(journalMode, is(not(equalToIgnoringCase("wal"))));
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    @Test
+    public void observeLiveData() {
+        UserDao dao = mDatabase.getUserDao();
+        LiveData<User> user1 = dao.liveUserById(1);
+        Observer<User> observer = startObserver(user1);
+        dao.insert(TestUtil.createUser(1));
+        verify(observer, timeout(3000).atLeastOnce())
+                .onChanged(argThat(user -> user != null && user.getId() == 1));
+        stopObserver(user1, observer);
+    }
+
+    @Test
+    public void observeLiveDataWithTransaction() {
+        UserDao dao = mDatabase.getUserDao();
+        LiveData<User> user1 = dao.liveUserByIdInTransaction(1);
+        Observer<User> observer = startObserver(user1);
+        dao.insert(TestUtil.createUser(1));
+        verify(observer, timeout(3000).atLeastOnce())
+                .onChanged(argThat(user -> user != null && user.getId() == 1));
+        stopObserver(user1, observer);
+    }
+
+    @Test
+    public void parallelWrites() throws InterruptedException, ExecutionException {
+        int numberOfThreads = 10;
+        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
+        ArrayList<Future<Boolean>> futures = new ArrayList<>();
+        for (int i = 0; i < numberOfThreads; i++) {
+            final int id = i + 1;
+            futures.add(i, executor.submit(() -> {
+                User user = TestUtil.createUser(id);
+                user.setName("user" + id);
+                mDatabase.getUserDao().insert(user);
+                return true;
+            }));
+        }
+        LiveData<List<User>> usersList = mDatabase.getUserDao().liveUsersListByName("user");
+        Observer<List<User>> observer = startObserver(usersList);
+        for (Future future : futures) {
+            assertThat(future.get(), is(true));
+        }
+        verify(observer, timeout(3000).atLeastOnce())
+                .onChanged(argThat(users -> users != null && users.size() == numberOfThreads));
+        stopObserver(usersList, observer);
+    }
+
+    @Test
+    public void readInBackground() throws InterruptedException, ExecutionException {
+        final UserDao dao = mDatabase.getUserDao();
+        final User user1 = TestUtil.createUser(1);
+        dao.insert(user1);
+        try {
+            mDatabase.beginTransaction();
+            dao.delete(user1);
+            ExecutorService executor = Executors.newSingleThreadExecutor();
+            Future<?> future = executor.submit(() ->
+                    assertThat(dao.load(1), is(equalTo(user1))));
+            future.get();
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+        assertThat(dao.count(), is(0));
+    }
+
+    @Test
+    @LargeTest
+    public void observeInvalidationInBackground() throws InterruptedException, ExecutionException {
+        final UserDao dao = mDatabase.getUserDao();
+        final User user1 = TestUtil.createUser(1);
+        final CountDownLatch observerRegistered = new CountDownLatch(1);
+        final CountDownLatch onInvalidatedCalled = new CountDownLatch(1);
+        dao.insert(user1);
+        Future future;
+        try {
+            mDatabase.beginTransaction();
+            dao.delete(user1);
+            future = Executors.newSingleThreadExecutor().submit(() -> {
+                // Adding this observer will be blocked by the surrounding transaction.
+                mDatabase.getInvalidationTracker().addObserver(
+                        new InvalidationTracker.Observer("User") {
+                            @Override
+                            public void onInvalidated(@NonNull Set<String> tables) {
+                                onInvalidatedCalled.countDown(); // This should not happen
+                            }
+                        });
+                observerRegistered.countDown();
+            });
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            assertThat(observerRegistered.getCount(), is(1L));
+            mDatabase.endTransaction();
+        }
+        assertThat(dao.count(), is(0));
+        assertThat(observerRegistered.await(3000, TimeUnit.MILLISECONDS), is(true));
+        future.get();
+        assertThat(onInvalidatedCalled.await(500, TimeUnit.MILLISECONDS), is(false));
+    }
+
+    @Test
+    public void invalidation() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mDatabase.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("User") {
+            @Override
+            public void onInvalidated(@NonNull Set<String> tables) {
+                assertThat(tables, hasSize(1));
+                assertThat(tables, hasItem("User"));
+                latch.countDown();
+            }
+        });
+        mDatabase.getUserDao().insert(TestUtil.createUser(1));
+        assertThat(latch.await(3000, TimeUnit.MILLISECONDS), is(true));
+    }
+
+    private static <T> Observer<T> startObserver(LiveData<T> liveData) {
+        @SuppressWarnings("unchecked")
+        Observer<T> observer = mock(Observer.class);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
+                liveData.observeForever(observer));
+        return observer;
+    }
+
+    private static <T> void stopObserver(LiveData<T> liveData, Observer<T> observer) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
+                liveData.removeObserver(observer));
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Address.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Address.java
new file mode 100644
index 0000000..e57cb87
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Address.java
@@ -0,0 +1,90 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Embedded;
+
+public class Address {
+    @ColumnInfo(name = "street")
+    private String mStreet;
+    @ColumnInfo(name = "state")
+    private String mState;
+    @ColumnInfo(name = "post_code")
+    private int mPostCode;
+    @Embedded
+    private Coordinates mCoordinates;
+
+    public String getStreet() {
+        return mStreet;
+    }
+
+    public void setStreet(String street) {
+        mStreet = street;
+    }
+
+    public String getState() {
+        return mState;
+    }
+
+    public void setState(String state) {
+        mState = state;
+    }
+
+    public int getPostCode() {
+        return mPostCode;
+    }
+
+    public void setPostCode(int postCode) {
+        mPostCode = postCode;
+    }
+
+    public Coordinates getCoordinates() {
+        return mCoordinates;
+    }
+
+    public void setCoordinates(Coordinates coordinates) {
+        mCoordinates = coordinates;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Address address = (Address) o;
+
+        if (mPostCode != address.mPostCode) return false;
+        if (mStreet != null ? !mStreet.equals(address.mStreet) : address.mStreet != null) {
+            return false;
+        }
+        if (mState != null ? !mState.equals(address.mState) : address.mState != null) {
+            return false;
+        }
+        return mCoordinates != null ? mCoordinates.equals(address.mCoordinates)
+                : address.mCoordinates == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mStreet != null ? mStreet.hashCode() : 0;
+        result = 31 * result + (mState != null ? mState.hashCode() : 0);
+        result = 31 * result + mPostCode;
+        result = 31 * result + (mCoordinates != null ? mCoordinates.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/AvgWeightByAge.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/AvgWeightByAge.java
new file mode 100644
index 0000000..fd7d87f
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/AvgWeightByAge.java
@@ -0,0 +1,87 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Ignore;
+
+@SuppressWarnings("unused")
+public class AvgWeightByAge {
+
+    private int mAge;
+
+    @ColumnInfo(name = "AVG(mWeight)")
+    private float mWeight;
+
+    public AvgWeightByAge() {
+    }
+
+    @Ignore
+    public AvgWeightByAge(int age, float weight) {
+        mAge = age;
+        mWeight = weight;
+    }
+
+    public int getAge() {
+        return mAge;
+    }
+
+    public void setAge(int age) {
+        mAge = age;
+    }
+
+    public float getWeight() {
+        return mWeight;
+    }
+
+    public void setWeight(float weight) {
+        mWeight = weight;
+    }
+
+    @Override
+    public String toString() {
+        return "AvgWeightByAge{"
+                + "mAge=" + mAge
+                + ", mWeight=" + mWeight
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        AvgWeightByAge that = (AvgWeightByAge) o;
+
+        //noinspection SimplifiableIfStatement
+        if (mAge != that.mAge) {
+            return false;
+        }
+        return Float.compare(that.mWeight, mWeight) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mAge;
+        result = 31 * result + (mWeight != +0.0f ? Float.floatToIntBits(mWeight) : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/BlobEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/BlobEntity.java
new file mode 100644
index 0000000..201773a
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/BlobEntity.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class BlobEntity {
+    @PrimaryKey
+    public long id;
+    public byte[] content;
+
+    public BlobEntity(long id, byte[] content) {
+        this.id = id;
+        this.content = content;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Coordinates.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Coordinates.java
new file mode 100644
index 0000000..e30342c
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Coordinates.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 androidx.room.integration.testapp.vo;
+
+public class Coordinates {
+    public double lat;
+    public double lng;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Coordinates that = (Coordinates) o;
+
+        if (Double.compare(that.lat, lat) != 0) return false;
+        return Double.compare(that.lng, lng) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        int result;
+        long temp;
+        temp = Double.doubleToLongBits(lat);
+        result = (int) (temp ^ (temp >>> 32));
+        temp = Double.doubleToLongBits(lng);
+        result = 31 * result + (int) (temp ^ (temp >>> 32));
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Day.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Day.java
new file mode 100644
index 0000000..7ec640a
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Day.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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 androidx.room.integration.testapp.vo;
+
+public enum Day {
+    MONDAY,
+    TUESDAY,
+    WEDNESDAY,
+    THURSDAY,
+    FRIDAY,
+    SATURDAY,
+    SUNDAY
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/EmbeddedUserAndAllPets.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/EmbeddedUserAndAllPets.java
new file mode 100644
index 0000000..01326b7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/EmbeddedUserAndAllPets.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+
+public class EmbeddedUserAndAllPets {
+    @Embedded
+    UserAndAllPets mUserAndAllPets;
+
+    public UserAndAllPets getUserAndAllPets() {
+        return mUserAndAllPets;
+    }
+
+    public void setUserAndAllPets(UserAndAllPets userAndAllPets) {
+        this.mUserAndAllPets = userAndAllPets;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/FunnyNamedEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/FunnyNamedEntity.java
new file mode 100644
index 0000000..038a8a4
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/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 androidx.room.integration.testapp.vo;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntAutoIncPKeyEntity.java
new file mode 100644
index 0000000..049ee9d
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntAutoIncPKeyEntity.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class IntAutoIncPKeyEntity {
+    @PrimaryKey(autoGenerate = true)
+    public int pKey;
+    public String data;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.java
new file mode 100644
index 0000000..6ddb217
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntegerAutoIncPKeyEntity.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class IntegerAutoIncPKeyEntity {
+    @PrimaryKey(autoGenerate = true)
+    public Integer pKey;
+    public String data;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntegerPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/IntegerPKeyEntity.java
new file mode 100644
index 0000000..6aa961b
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class IntegerPKeyEntity {
+    @PrimaryKey
+    public Integer pKey;
+    public String data;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/NameAndLastName.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/NameAndLastName.java
new file mode 100644
index 0000000..e2595e3
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/NameAndLastName.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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 androidx.room.integration.testapp.vo;
+
+
+public class NameAndLastName {
+    private String mName;
+    private String mLastName;
+
+    public NameAndLastName(String name, String lastName) {
+        mName = name;
+        mLastName = lastName;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getLastName() {
+        return mLastName;
+    }
+
+    @SuppressWarnings("SimplifiableIfStatement")
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        NameAndLastName that = (NameAndLastName) o;
+
+        if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
+        return mLastName != null ? mLastName.equals(that.mLastName) : that.mLastName == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mName != null ? mName.hashCode() : 0;
+        result = 31 * result + (mLastName != null ? mLastName.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/ObjectPKeyEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/ObjectPKeyEntity.java
new file mode 100644
index 0000000..0a18a9a
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/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 androidx.room.integration.testapp.vo;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@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/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Pet.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Pet.java
new file mode 100644
index 0000000..7eb04a0
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Pet.java
@@ -0,0 +1,99 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import java.util.Date;
+
+@Entity
+public class Pet {
+    @PrimaryKey
+    private int mPetId;
+    private int mUserId;
+    @ColumnInfo(name = "mPetName")
+    private String mName;
+
+    private Date mAdoptionDate;
+
+    public int getPetId() {
+        return mPetId;
+    }
+
+    public void setPetId(int petId) {
+        mPetId = petId;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public void setUserId(int userId) {
+        mUserId = userId;
+    }
+
+    public Date getAdoptionDate() {
+        return mAdoptionDate;
+    }
+
+    public void setAdoptionDate(Date adoptionDate) {
+        mAdoptionDate = adoptionDate;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Pet pet = (Pet) o;
+
+        if (mPetId != pet.mPetId) return false;
+        if (mUserId != pet.mUserId) return false;
+        if (mName != null ? !mName.equals(pet.mName) : pet.mName != null) return false;
+        return mAdoptionDate != null ? mAdoptionDate.equals(pet.mAdoptionDate)
+                : pet.mAdoptionDate == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mPetId;
+        result = 31 * result + mUserId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + (mAdoptionDate != null ? mAdoptionDate.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Pet{"
+                + "mPetId=" + mPetId
+                + ", mUserId=" + mUserId
+                + ", mName='" + mName + '\''
+                + ", mAdoptionDate=" + mAdoptionDate
+                + '}';
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetAndToys.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetAndToys.java
new file mode 100644
index 0000000..4f1d225
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetAndToys.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class PetAndToys {
+    @Embedded
+    public Pet pet;
+    @Relation(parentColumn = "mPetId", entityColumn = "mPetId")
+    public List<Toy> toys;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetCouple.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetCouple.java
new file mode 100644
index 0000000..df612a7
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetCouple.java
@@ -0,0 +1,61 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.annotation.NonNull;
+import androidx.room.Embedded;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+import androidx.room.RoomWarnings;
+
+@Entity
+@SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
+public class PetCouple {
+    @PrimaryKey
+    @NonNull
+    public String id;
+    @Embedded(prefix = "male_")
+    public Pet male;
+    @Embedded(prefix = "female_")
+    private Pet mFemale;
+
+    public Pet getFemale() {
+        return mFemale;
+    }
+
+    public void setFemale(Pet female) {
+        mFemale = female;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PetCouple petCouple = (PetCouple) o;
+
+        if (male != null ? !male.equals(petCouple.male) : petCouple.male != null) return false;
+        return mFemale != null ? mFemale.equals(petCouple.mFemale) : petCouple.mFemale == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = male != null ? male.hashCode() : 0;
+        result = 31 * result + (mFemale != null ? mFemale.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetWithToyIds.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetWithToyIds.java
new file mode 100644
index 0000000..7510444
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetWithToyIds.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+import androidx.room.Ignore;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class PetWithToyIds {
+    @Embedded
+    public final Pet pet;
+    @Relation(parentColumn = "mPetId", entityColumn = "mPetId", projection = "mId",
+            entity = Toy.class)
+    public List<Integer> toyIds;
+
+    // for the relation
+    public PetWithToyIds(Pet pet) {
+        this.pet = pet;
+    }
+
+    @Ignore
+    public PetWithToyIds(Pet pet, List<Integer> toyIds) {
+        this.pet = pet;
+        this.toyIds = toyIds;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PetWithToyIds that = (PetWithToyIds) o;
+
+        if (pet != null ? !pet.equals(that.pet) : that.pet != null) return false;
+        return toyIds != null ? toyIds.equals(that.toyIds) : that.toyIds == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = pet != null ? pet.hashCode() : 0;
+        result = 31 * result + (toyIds != null ? toyIds.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "PetWithToyIds{"
+                + "pet=" + pet
+                + ", toyIds=" + toyIds
+                + '}';
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetsToys.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetsToys.java
new file mode 100644
index 0000000..4196067
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/PetsToys.java
@@ -0,0 +1,56 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Relation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PetsToys {
+    public int petId;
+    @Relation(parentColumn = "petId", entityColumn = "mPetId")
+    public List<Toy> toys;
+
+    public PetsToys() {
+        toys = new ArrayList<>();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        PetsToys petsToys = (PetsToys) o;
+
+        if (petId != petsToys.petId) {
+            return false;
+        }
+        return toys != null ? toys.equals(petsToys.toys) : petsToys.toys == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = petId;
+        result = 31 * result + (toys != null ? toys.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Product.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Product.java
new file mode 100644
index 0000000..67c903f
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Product.java
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity(tableName = "products")
+public class Product {
+
+    @PrimaryKey(autoGenerate = true)
+    public final int id;
+
+    @NonNull
+    public final String name;
+
+    public Product(int id, @NonNull String name) {
+        this.id = id;
+        this.name = name;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/School.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/School.java
new file mode 100644
index 0000000..610bcd3
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/School.java
@@ -0,0 +1,95 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class School {
+    @PrimaryKey
+    private int mId;
+    private String mName;
+    @Embedded(prefix = "address_")
+    public Address address;
+
+    @Embedded(prefix = "manager_")
+    private User mManager;
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        mId = id;
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+
+    public void setAddress(Address address) {
+        this.address = address;
+    }
+
+    public User getManager() {
+        return mManager;
+    }
+
+    public void setManager(User manager) {
+        mManager = manager;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        School school = (School) o;
+
+        if (mId != school.mId) {
+            return false;
+        }
+        if (mName != null ? !mName.equals(school.mName) : school.mName != null) {
+            return false;
+        }
+        if (address != null ? !address.equals(school.address) : school.address != null) {
+            return false;
+        }
+        return mManager != null ? mManager.equals(school.mManager) : school.mManager == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + (address != null ? address.hashCode() : 0);
+        result = 31 * result + (mManager != null ? mManager.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/SchoolRef.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/SchoolRef.java
new file mode 100644
index 0000000..cbcfd75
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/SchoolRef.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 androidx.room.integration.testapp.vo;
+
+public class SchoolRef extends School {
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Toy.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Toy.java
new file mode 100644
index 0000000..ebc64e6
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/Toy.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+/**
+ * The toys of a pet.
+ */
+@Entity
+public class Toy {
+    @PrimaryKey(autoGenerate = true)
+    private int mId;
+    private String mName;
+    private int mPetId;
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        mId = id;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public int getPetId() {
+        return mPetId;
+    }
+
+    public void setPetId(int petId) {
+        mPetId = petId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Toy toy = (Toy) o;
+
+        if (mId != toy.mId) return false;
+        if (mPetId != toy.mPetId) return false;
+        return mName != null ? mName.equals(toy.mName) : toy.mName == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + mPetId;
+        return result;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/User.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/User.java
new file mode 100644
index 0000000..af86b7a
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/User.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.vo;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+import androidx.room.TypeConverters;
+import androidx.room.integration.testapp.TestDatabase;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+@Entity
+@TypeConverters({TestDatabase.Converters.class})
+public class User {
+
+    @PrimaryKey
+    private int mId;
+
+    private String mName;
+
+    private String mLastName;
+
+    private int mAge;
+
+    private boolean mAdmin;
+
+    private float mWeight;
+
+    private Date mBirthday;
+
+    @ColumnInfo(name = "custommm", collate = ColumnInfo.NOCASE)
+    private String mCustomField;
+
+    // bit flags
+    private Set<Day> mWorkDays = new HashSet<>();
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        this.mId = id;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        this.mName = name;
+    }
+
+    public String getLastName() {
+        return mLastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.mLastName = lastName;
+    }
+
+    public int getAge() {
+        return mAge;
+    }
+
+    public void setAge(int age) {
+        this.mAge = age;
+    }
+
+    public boolean isAdmin() {
+        return mAdmin;
+    }
+
+    public void setAdmin(boolean admin) {
+        mAdmin = admin;
+    }
+
+    public float getWeight() {
+        return mWeight;
+    }
+
+    public void setWeight(float weight) {
+        mWeight = weight;
+    }
+
+    public Date getBirthday() {
+        return mBirthday;
+    }
+
+    public void setBirthday(Date birthday) {
+        mBirthday = birthday;
+    }
+
+    public String getCustomField() {
+        return mCustomField;
+    }
+
+    public void setCustomField(String customField) {
+        mCustomField = customField;
+    }
+
+    public Set<Day> getWorkDays() {
+        return mWorkDays;
+    }
+
+    public void setWorkDays(
+            Set<Day> workDays) {
+        mWorkDays = workDays;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        User user = (User) o;
+
+        if (mId != user.mId) return false;
+        if (mAge != user.mAge) return false;
+        if (mAdmin != user.mAdmin) return false;
+        if (Float.compare(user.mWeight, mWeight) != 0) return false;
+        if (mName != null ? !mName.equals(user.mName) : user.mName != null) return false;
+        if (mLastName != null ? !mLastName.equals(user.mLastName) : user.mLastName != null) {
+            return false;
+        }
+        if (mBirthday != null ? !mBirthday.equals(user.mBirthday) : user.mBirthday != null) {
+            return false;
+        }
+        if (mCustomField != null ? !mCustomField.equals(user.mCustomField)
+                : user.mCustomField != null) {
+            return false;
+        }
+        return mWorkDays != null ? mWorkDays.equals(user.mWorkDays) : user.mWorkDays == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + (mLastName != null ? mLastName.hashCode() : 0);
+        result = 31 * result + mAge;
+        result = 31 * result + (mAdmin ? 1 : 0);
+        result = 31 * result + (mWeight != +0.0f ? Float.floatToIntBits(mWeight) : 0);
+        result = 31 * result + (mBirthday != null ? mBirthday.hashCode() : 0);
+        result = 31 * result + (mCustomField != null ? mCustomField.hashCode() : 0);
+        result = 31 * result + (mWorkDays != null ? mWorkDays.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "User{"
+                + "mId=" + mId
+                + ", mName='" + mName + '\''
+                + ", mLastName='" + mLastName + '\''
+                + ", mAge=" + mAge
+                + ", mAdmin=" + mAdmin
+                + ", mWeight=" + mWeight
+                + ", mBirthday=" + mBirthday
+                + ", mCustomField='" + mCustomField + '\''
+                + ", mWorkDays=" + mWorkDays
+                + '}';
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndAllPets.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndAllPets.java
new file mode 100644
index 0000000..90332d4
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndAllPets.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class UserAndAllPets {
+    @Embedded
+    public User user;
+    @Relation(parentColumn = "mId", entityColumn = "mUserId")
+    public List<Pet> pets;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPet.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPet.java
new file mode 100644
index 0000000..6b54088
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPet.java
@@ -0,0 +1,41 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+import androidx.room.Embedded;
+
+public class UserAndPet {
+    @Embedded
+    private User mUser;
+    @Embedded
+    private Pet mPet;
+
+    public User getUser() {
+        return mUser;
+    }
+
+    public void setUser(User user) {
+        mUser = user;
+    }
+
+    public Pet getPet() {
+        return mPet;
+    }
+
+    public void setPet(Pet pet) {
+        mPet = pet;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPetAdoptionDates.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPetAdoptionDates.java
new file mode 100644
index 0000000..24d13a6
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPetAdoptionDates.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+import androidx.room.Ignore;
+import androidx.room.Relation;
+
+import java.util.Date;
+import java.util.List;
+
+public class UserAndPetAdoptionDates {
+    @Embedded
+    public final User user;
+    @Relation(entity = Pet.class,
+            parentColumn = "mId",
+            entityColumn = "mUserId",
+            projection = "mAdoptionDate")
+    public List<Date> petAdoptionDates;
+
+    public UserAndPetAdoptionDates(User user) {
+        this.user = user;
+    }
+
+    @Ignore
+    public UserAndPetAdoptionDates(User user, List<Date> petAdoptionDates) {
+        this.user = user;
+        this.petAdoptionDates = petAdoptionDates;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        UserAndPetAdoptionDates that = (UserAndPetAdoptionDates) o;
+
+        if (user != null ? !user.equals(that.user) : that.user != null) return false;
+        return petAdoptionDates != null ? petAdoptionDates.equals(that.petAdoptionDates)
+                : that.petAdoptionDates == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = user != null ? user.hashCode() : 0;
+        result = 31 * result + (petAdoptionDates != null ? petAdoptionDates.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "UserAndPetAdoptionDates{"
+                + "user=" + user
+                + ", petAdoptionDates=" + petAdoptionDates
+                + '}';
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPetNonNull.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPetNonNull.java
new file mode 100644
index 0000000..ef60a1a
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserAndPetNonNull.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 androidx.room.integration.testapp.vo;
+
+import androidx.annotation.NonNull;
+import androidx.room.Embedded;
+
+public class UserAndPetNonNull {
+    @Embedded
+    private User mUser;
+    @Embedded
+    @NonNull
+    private Pet mPet;
+
+    public User getUser() {
+        return mUser;
+    }
+
+    public void setUser(User user) {
+        mUser = user;
+    }
+
+    public Pet getPet() {
+        return mPet;
+    }
+
+    public void setPet(Pet pet) {
+        mPet = pet;
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserIdAndPetNames.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserIdAndPetNames.java
new file mode 100644
index 0000000..96788c9
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserIdAndPetNames.java
@@ -0,0 +1,33 @@
+/*
+ * 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 androidx.room.integration.testapp.vo;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Relation;
+
+import java.util.List;
+
+/**
+ * Same as Pet class but only keeps name and user id
+ */
+public class UserIdAndPetNames {
+    @ColumnInfo(name = "mId")
+    public int userId;
+    @Relation(entity = Pet.class, parentColumn = "mId", entityColumn = "mUserId",
+            projection = "mPetName")
+    public List<String> names;
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserWithPetsAndToys.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserWithPetsAndToys.java
new file mode 100644
index 0000000..ffad720
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/UserWithPetsAndToys.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 androidx.room.integration.testapp.vo;
+
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class UserWithPetsAndToys {
+    @Embedded
+    public User user;
+    @Relation(entity = Pet.class, parentColumn = "mId", entityColumn = "mUserId")
+    public List<PetAndToys> pets;
+}
diff --git a/room/integration-tests/testapp/src/main/AndroidManifest.xml b/room/integration-tests/testapp/src/main/AndroidManifest.xml
index 02c1975..245f9c0 100644
--- a/room/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/room/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -15,12 +15,12 @@
   ~ limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.persistence.room.integration.testapp">
+          package="androidx.room.integration.testapp">
     <application
         android:allowBackup="true"
         android:supportsRtl="true">
         <activity
-            android:name=".RoomPagedListActivity"
+            android:name="androidx.room.integration.testapp.RoomPagedListActivity"
             android:label="Room Live PagedList"
             android:theme="@style/Theme.AppCompat">
             <intent-filter>
@@ -29,7 +29,7 @@
             </intent-filter>
         </activity>
         <activity
-            android:name=".RoomKeyedPagedListActivity"
+            android:name="androidx.room.integration.testapp.RoomKeyedPagedListActivity"
             android:label="Keyed Live PagedList"
             android:theme="@style/Theme.AppCompat">
             <intent-filter>
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
deleted file mode 100644
index d8cd8d4..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp;
-
-import android.app.Application;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.lifecycle.AndroidViewModel;
-import android.arch.lifecycle.LiveData;
-import android.arch.paging.DataSource;
-import android.arch.paging.LivePagedListBuilder;
-import android.arch.paging.PagedList;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.integration.testapp.database.Customer;
-import android.arch.persistence.room.integration.testapp.database.LastNameAscCustomerDataSource;
-import android.arch.persistence.room.integration.testapp.database.SampleDatabase;
-import android.support.annotation.WorkerThread;
-
-import java.util.UUID;
-
-/**
- * Sample database-backed view model of Customers
- */
-public class CustomerViewModel extends AndroidViewModel {
-    private SampleDatabase mDatabase;
-    private LiveData<PagedList<Customer>> mLiveCustomerList;
-
-    public CustomerViewModel(Application application) {
-        super(application);
-        createDb();
-    }
-
-    private void createDb() {
-        mDatabase = Room.databaseBuilder(this.getApplication(),
-                SampleDatabase.class, "customerDatabase").build();
-
-        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
-            @Override
-            public void run() {
-                // fill with some simple data
-                int customerCount = mDatabase.getCustomerDao().countCustomers();
-                if (customerCount == 0) {
-                    Customer[] initialCustomers = new Customer[10];
-                    for (int i = 0; i < 10; i++) {
-                        initialCustomers[i] = createCustomer();
-                    }
-                    mDatabase.getCustomerDao().insertAll(initialCustomers);
-                }
-
-            }
-        });
-    }
-
-    @WorkerThread
-    private Customer createCustomer() {
-        Customer customer = new Customer();
-        customer.setName(UUID.randomUUID().toString());
-        customer.setLastName(UUID.randomUUID().toString());
-        return customer;
-    }
-
-    void insertCustomer() {
-        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
-            @Override
-            public void run() {
-                mDatabase.getCustomerDao().insert(createCustomer());
-            }
-        });
-    }
-
-    private static <K> LiveData<PagedList<Customer>> getLivePagedList(
-            K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) {
-        PagedList.Config config = new PagedList.Config.Builder()
-                .setPageSize(10)
-                .setEnablePlaceholders(false)
-                .build();
-        return new LivePagedListBuilder<>(dataSourceFactory, config)
-                .setInitialLoadKey(initialLoadKey)
-                .build();
-    }
-
-    LiveData<PagedList<Customer>> getLivePagedList(int position) {
-        if (mLiveCustomerList == null) {
-            mLiveCustomerList =
-                    getLivePagedList(position, mDatabase.getCustomerDao().loadPagedAgeOrder());
-        }
-        return mLiveCustomerList;
-    }
-
-    LiveData<PagedList<Customer>> getLivePagedList(String key) {
-        if (mLiveCustomerList == null) {
-            mLiveCustomerList =
-                    getLivePagedList(key, LastNameAscCustomerDataSource.factory(mDatabase));
-        }
-        return mLiveCustomerList;
-    }
-}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/PagedListCustomerAdapter.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/PagedListCustomerAdapter.java
deleted file mode 100644
index eacea99..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/PagedListCustomerAdapter.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp;
-
-import android.arch.paging.PagedList;
-import android.arch.paging.PagedListAdapter;
-import android.arch.persistence.room.integration.testapp.database.Customer;
-import android.arch.persistence.room.integration.testapp.database.LastNameAscCustomerDataSource;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.RecyclerView;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-/**
- * Sample adapter which uses a AsyncPagedListDiffer.
- */
-class PagedListCustomerAdapter extends PagedListAdapter<Customer, RecyclerView.ViewHolder> {
-    private RecyclerView mRecyclerView;
-    private boolean mSetObserved;
-    private int mScrollToPosition = -1;
-    private String mScrollToKey = null;
-
-    PagedListCustomerAdapter() {
-        super(Customer.DIFF_CALLBACK);
-    }
-
-    void setScrollToPosition(int position) {
-        mScrollToPosition = position;
-    }
-
-    void setScrollToKey(String key) {
-        mScrollToKey = key;
-    }
-
-    @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(
-                new TextView(parent.getContext())) {};
-        holder.itemView.setMinimumHeight(400);
-        return holder;
-    }
-
-    @Override
-    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-        Customer customer = getItem(position);
-
-        if (customer != null) {
-            ((TextView) (holder.itemView)).setText(customer.getId() + " " + customer.getLastName());
-        } else {
-            ((TextView) (holder.itemView)).setText(R.string.loading);
-        }
-    }
-
-    private static int findKeyInPagedList(@NonNull String key, @NonNull PagedList<Customer> list) {
-        for (int i = 0; i < list.size(); i++) {
-            @Nullable Customer customer = list.get(i);
-            if (customer != null
-                    && LastNameAscCustomerDataSource.getKeyStatic(customer).equals(key)) {
-                return i;
-            }
-        }
-        return 0; // couldn't find, fall back to 0 - could alternately search with comparator
-    }
-
-    @Override
-    public void submitList(PagedList<Customer> pagedList) {
-        super.submitList(pagedList);
-
-        if (pagedList != null) {
-            final boolean firstSet = !mSetObserved;
-            mSetObserved = true;
-
-            if (firstSet
-                    && mRecyclerView != null
-                    && (mScrollToPosition >= 0 || mScrollToKey != null)) {
-                int localScrollToPosition;
-                if (mScrollToKey != null) {
-                    localScrollToPosition = findKeyInPagedList(mScrollToKey, pagedList);
-                    mScrollToKey = null;
-                } else {
-                    // if there's 20 items unloaded items (without placeholders holding the spots)
-                    // at the beginning of list, we subtract 20 from saved position
-                    localScrollToPosition = mScrollToPosition - pagedList.getPositionOffset();
-                }
-                mRecyclerView.scrollToPosition(localScrollToPosition);
-            }
-        }
-    }
-
-    @Override
-    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
-        mRecyclerView = recyclerView;
-    }
-
-    @Override
-    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
-        mRecyclerView = null;
-    }
-}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomKeyedPagedListActivity.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomKeyedPagedListActivity.java
deleted file mode 100644
index 17ac4ce..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomKeyedPagedListActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp;
-
-/**
- * Sample PagedList activity which uses Room.
- */
-public class RoomKeyedPagedListActivity extends RoomPagedListActivity {
-    @Override
-    protected boolean useKeyedQuery() {
-        return true;
-    }
-}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
deleted file mode 100644
index 66620ef..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp;
-
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.ViewModelProviders;
-import android.arch.paging.PagedList;
-import android.arch.persistence.room.integration.testapp.database.Customer;
-import android.arch.persistence.room.integration.testapp.database.LastNameAscCustomerDataSource;
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.widget.Button;
-
-/**
- * Sample PagedList activity which uses Room.
- */
-public class RoomPagedListActivity extends AppCompatActivity {
-
-    private RecyclerView mRecyclerView;
-    private PagedListCustomerAdapter mAdapter;
-
-    private static final String STRING_KEY = "STRING_KEY";
-    private static final String INT_KEY = "INT_KEY";
-
-    @Override
-    protected void onCreate(final Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_recycler_view);
-        final CustomerViewModel viewModel = ViewModelProviders.of(this)
-                .get(CustomerViewModel.class);
-
-        mRecyclerView = findViewById(R.id.recyclerview);
-        mAdapter = new PagedListCustomerAdapter();
-        mRecyclerView.setAdapter(mAdapter);
-
-        LiveData<PagedList<Customer>> livePagedList;
-        if (useKeyedQuery()) {
-            String key = null;
-            if (savedInstanceState != null) {
-                key = savedInstanceState.getString(STRING_KEY);
-                mAdapter.setScrollToKey(key);
-            }
-            livePagedList = viewModel.getLivePagedList(key);
-        } else {
-            int position = 0;
-            if (savedInstanceState != null) {
-                position = savedInstanceState.getInt(INT_KEY);
-                mAdapter.setScrollToPosition(position);
-            }
-            livePagedList = viewModel.getLivePagedList(position);
-        }
-        livePagedList.observe(this, items -> mAdapter.submitList(items));
-        final Button button = findViewById(R.id.addButton);
-        button.setOnClickListener(v -> viewModel.insertCustomer());
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        PagedList<Customer> list = mAdapter.getCurrentList();
-        if (list == null) {
-            // Can't find anything to restore
-            return;
-        }
-
-        LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
-        final int targetPosition = layoutManager.findFirstVisibleItemPosition();
-
-        if (useKeyedQuery()) {
-            Customer customer = list.get(targetPosition);
-            if (customer != null) {
-                String key = LastNameAscCustomerDataSource.getKeyStatic(customer);
-                outState.putString(STRING_KEY, key);
-            }
-        } else {
-            // NOTE: in the general case, we can't just rely on RecyclerView/LinearLayoutManager to
-            // preserve position, because of position offset which is present when using an
-            // uncounted, non-keyed source).
-            int absolutePosition = targetPosition + list.getPositionOffset();
-            outState.putInt(INT_KEY, absolutePosition);
-        }
-    }
-
-    protected boolean useKeyedQuery() {
-        return false;
-    }
-}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/Customer.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/Customer.java
deleted file mode 100644
index 54fa531..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/Customer.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.database;
-
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-import android.support.annotation.NonNull;
-import android.support.v7.util.DiffUtil;
-
-/**
- * Sample entity
- */
-@Entity
-public class Customer {
-
-    @PrimaryKey(autoGenerate = true)
-    private int mId;
-
-    private String mName;
-
-    private String mLastName;
-
-    public int getId() {
-        return mId;
-    }
-
-    public void setId(int id) {
-        this.mId = id;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public void setName(String name) {
-        this.mName = name;
-    }
-
-    public String getLastName() {
-        return mLastName;
-    }
-
-    public void setLastName(String lastName) {
-        this.mLastName = lastName;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        Customer customer = (Customer) o;
-
-        if (mId != customer.mId) {
-            return false;
-        }
-        if (mName != null ? !mName.equals(customer.mName) : customer.mName != null) {
-            return false;
-        }
-        return mLastName != null ? mLastName.equals(customer.mLastName)
-                : customer.mLastName == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mId;
-        result = 31 * result + (mName != null ? mName.hashCode() : 0);
-        result = 31 * result + (mLastName != null ? mLastName.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "Customer{"
-                + "mId=" + mId
-                + ", mName='" + mName + '\''
-                + ", mLastName='" + mLastName + '\''
-                + '}';
-    }
-
-    public static final DiffUtil.ItemCallback<Customer> DIFF_CALLBACK =
-            new DiffUtil.ItemCallback<Customer>() {
-        @Override
-        public boolean areContentsTheSame(@NonNull Customer oldItem, @NonNull Customer newItem) {
-            return oldItem.equals(newItem);
-        }
-
-        @Override
-        public boolean areItemsTheSame(@NonNull Customer oldItem, @NonNull Customer newItem) {
-            return oldItem.getId() == newItem.getId();
-        }
-    };
-}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
deleted file mode 100644
index db45dc4..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.database;
-
-import android.arch.paging.DataSource;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
-
-import java.util.List;
-
-/**
- * Simple Customer DAO for Room Customer list sample.
- */
-@Dao
-public interface CustomerDao {
-
-    /**
-     * Insert a customer
-     * @param customer Customer.
-     */
-    @Insert
-    void insert(Customer customer);
-
-    /**
-     * Insert multiple customers.
-     * @param customers Customers.
-     */
-    @Insert
-    void insertAll(Customer[] customers);
-
-    /**
-     * @return DataSource.Factory of customers, ordered by last name. Use
-     * {@link android.arch.paging.LivePagedListBuilder} to get a LiveData of PagedLists.
-     */
-    @Query("SELECT * FROM customer ORDER BY mLastName ASC")
-    DataSource.Factory<Integer, Customer> loadPagedAgeOrder();
-
-    /**
-     * @return number of customers
-     */
-    @Query("SELECT COUNT(*) FROM customer")
-    int countCustomers();
-
-    // Keyed
-
-    @Query("SELECT * from customer ORDER BY mLastName DESC LIMIT :limit")
-    List<Customer> customerNameInitial(int limit);
-
-    @Query("SELECT * from customer WHERE mLastName < :key ORDER BY mLastName DESC LIMIT :limit")
-    List<Customer> customerNameLoadAfter(String key, int limit);
-
-    @Query("SELECT COUNT(*) from customer WHERE mLastName < :key ORDER BY mLastName DESC")
-    int customerNameCountAfter(String key);
-
-    @Query("SELECT * from customer WHERE mLastName > :key ORDER BY mLastName ASC LIMIT :limit")
-    List<Customer> customerNameLoadBefore(String key, int limit);
-
-    @Query("SELECT COUNT(*) from customer WHERE mLastName > :key ORDER BY mLastName ASC")
-    int customerNameCountBefore(String key);
-}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
deleted file mode 100644
index 6278bc2..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room.integration.testapp.database;
-
-import android.arch.paging.DataSource;
-import android.arch.paging.ItemKeyedDataSource;
-import android.arch.persistence.room.InvalidationTracker;
-import android.support.annotation.NonNull;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Sample Room keyed data source.
- */
-public class LastNameAscCustomerDataSource extends ItemKeyedDataSource<String, Customer> {
-    private final CustomerDao mCustomerDao;
-    @SuppressWarnings("FieldCanBeLocal")
-    private final InvalidationTracker.Observer mObserver;
-    private SampleDatabase mDb;
-
-    public static Factory<String, Customer> factory(final SampleDatabase db) {
-        return new Factory<String, Customer>() {
-            @Override
-            public DataSource<String, Customer> create() {
-                return new LastNameAscCustomerDataSource(db);
-            }
-        };
-    }
-
-    /**
-     * Create a DataSource from the customer table of the given database
-     */
-    private LastNameAscCustomerDataSource(SampleDatabase db) {
-        mDb = db;
-        mCustomerDao = db.getCustomerDao();
-        mObserver = new InvalidationTracker.Observer("customer") {
-            @Override
-            public void onInvalidated(@NonNull Set<String> tables) {
-                invalidate();
-            }
-        };
-        db.getInvalidationTracker().addWeakObserver(mObserver);
-    }
-
-    @Override
-    public boolean isInvalid() {
-        mDb.getInvalidationTracker().refreshVersionsSync();
-        return super.isInvalid();
-    }
-
-    @NonNull
-    public static String getKeyStatic(@NonNull Customer customer) {
-        return customer.getLastName();
-    }
-
-    @NonNull
-    @Override
-    public String getKey(@NonNull Customer customer) {
-        return getKeyStatic(customer);
-    }
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams<String> params,
-            @NonNull LoadInitialCallback<Customer> callback) {
-        String customerName = params.requestedInitialKey;
-        List<Customer> list;
-        if (customerName != null) {
-            // initial keyed load - load before 'customerName',
-            // and load after last item in before list
-            int pageSize = params.requestedLoadSize / 2;
-            String key = customerName;
-            list = mCustomerDao.customerNameLoadBefore(key, pageSize);
-            Collections.reverse(list);
-            if (!list.isEmpty()) {
-                key = getKey(list.get(list.size() - 1));
-            }
-            list.addAll(mCustomerDao.customerNameLoadAfter(key, pageSize));
-        } else {
-            list = mCustomerDao.customerNameInitial(params.requestedLoadSize);
-        }
-
-        if (params.placeholdersEnabled && !list.isEmpty()) {
-            String firstKey = getKey(list.get(0));
-            String lastKey = getKey(list.get(list.size() - 1));
-
-            // only bother counting if placeholders are desired
-            final int position = mCustomerDao.customerNameCountBefore(firstKey);
-            final int count = position + list.size() + mCustomerDao.customerNameCountAfter(lastKey);
-            callback.onResult(list, position, count);
-        } else {
-            callback.onResult(list);
-        }
-    }
-
-    @Override
-    public void loadAfter(@NonNull LoadParams<String> params,
-            @NonNull LoadCallback<Customer> callback) {
-        callback.onResult(mCustomerDao.customerNameLoadAfter(params.key, params.requestedLoadSize));
-    }
-
-    @Override
-    public void loadBefore(@NonNull LoadParams<String> params,
-            @NonNull LoadCallback<Customer> callback) {
-        List<Customer> list = mCustomerDao.customerNameLoadBefore(
-                params.key, params.requestedLoadSize);
-        Collections.reverse(list);
-        callback.onResult(list);
-    }
-}
-
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
deleted file mode 100644
index 9020eb1..0000000
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.database;
-
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.RoomDatabase;
-
-/**
- * Sample database of customers.
- */
-@Database(entities = {Customer.class},
-        version = 1, exportSchema = false)
-public abstract class SampleDatabase extends RoomDatabase {
-    /**
-     * @return customer dao.
-     */
-    public abstract CustomerDao getCustomerDao();
-}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/CustomerViewModel.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/CustomerViewModel.java
new file mode 100644
index 0000000..eca2552
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/CustomerViewModel.java
@@ -0,0 +1,111 @@
+/*
+ * 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 androidx.room.integration.testapp;
+
+import android.app.Application;
+
+import androidx.annotation.WorkerThread;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.paging.DataSource;
+import androidx.paging.LivePagedListBuilder;
+import androidx.paging.PagedList;
+import androidx.room.Room;
+import androidx.room.integration.testapp.database.Customer;
+import androidx.room.integration.testapp.database.LastNameAscCustomerDataSource;
+import androidx.room.integration.testapp.database.SampleDatabase;
+
+import java.util.UUID;
+
+/**
+ * Sample database-backed view model of Customers
+ */
+public class CustomerViewModel extends AndroidViewModel {
+    private SampleDatabase mDatabase;
+    private LiveData<PagedList<Customer>> mLiveCustomerList;
+
+    public CustomerViewModel(Application application) {
+        super(application);
+        createDb();
+    }
+
+    private void createDb() {
+        mDatabase = Room.databaseBuilder(this.getApplication(),
+                SampleDatabase.class, "customerDatabase").build();
+
+        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+            @Override
+            public void run() {
+                // fill with some simple data
+                int customerCount = mDatabase.getCustomerDao().countCustomers();
+                if (customerCount == 0) {
+                    Customer[] initialCustomers = new Customer[10];
+                    for (int i = 0; i < 10; i++) {
+                        initialCustomers[i] = createCustomer();
+                    }
+                    mDatabase.getCustomerDao().insertAll(initialCustomers);
+                }
+
+            }
+        });
+    }
+
+    @WorkerThread
+    private Customer createCustomer() {
+        Customer customer = new Customer();
+        customer.setName(UUID.randomUUID().toString());
+        customer.setLastName(UUID.randomUUID().toString());
+        return customer;
+    }
+
+    void insertCustomer() {
+        ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+            @Override
+            public void run() {
+                mDatabase.getCustomerDao().insert(createCustomer());
+            }
+        });
+    }
+
+    private static <K> LiveData<PagedList<Customer>> getLivePagedList(
+            K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) {
+        PagedList.Config config = new PagedList.Config.Builder()
+                .setPageSize(10)
+                .setEnablePlaceholders(false)
+                .build();
+        return new LivePagedListBuilder<>(dataSourceFactory, config)
+                .setInitialLoadKey(initialLoadKey)
+                .build();
+    }
+
+    LiveData<PagedList<Customer>> getLivePagedList(int position) {
+        if (mLiveCustomerList == null) {
+            mLiveCustomerList =
+                    getLivePagedList(position, mDatabase.getCustomerDao().loadPagedAgeOrder());
+        }
+        return mLiveCustomerList;
+    }
+
+    LiveData<PagedList<Customer>> getLivePagedList(String key) {
+        if (mLiveCustomerList == null) {
+            mLiveCustomerList =
+                    getLivePagedList(key, LastNameAscCustomerDataSource.factory(mDatabase));
+        }
+        return mLiveCustomerList;
+    }
+}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/PagedListCustomerAdapter.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/PagedListCustomerAdapter.java
new file mode 100644
index 0000000..8c0a305
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/PagedListCustomerAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * 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 androidx.room.integration.testapp;
+
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.paging.PagedList;
+import androidx.paging.PagedListAdapter;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.room.integration.testapp.database.Customer;
+import androidx.room.integration.testapp.database.LastNameAscCustomerDataSource;
+
+/**
+ * Sample adapter which uses a AsyncPagedListDiffer.
+ */
+class PagedListCustomerAdapter extends PagedListAdapter<Customer, RecyclerView.ViewHolder> {
+    private RecyclerView mRecyclerView;
+    private boolean mSetObserved;
+    private int mScrollToPosition = -1;
+    private String mScrollToKey = null;
+
+    PagedListCustomerAdapter() {
+        super(Customer.DIFF_CALLBACK);
+    }
+
+    void setScrollToPosition(int position) {
+        mScrollToPosition = position;
+    }
+
+    void setScrollToKey(String key) {
+        mScrollToKey = key;
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(
+                new TextView(parent.getContext())) {};
+        holder.itemView.setMinimumHeight(400);
+        return holder;
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        Customer customer = getItem(position);
+
+        if (customer != null) {
+            ((TextView) (holder.itemView)).setText(customer.getId() + " " + customer.getLastName());
+        } else {
+            ((TextView) (holder.itemView)).setText(R.string.loading);
+        }
+    }
+
+    private static int findKeyInPagedList(@NonNull String key, @NonNull PagedList<Customer> list) {
+        for (int i = 0; i < list.size(); i++) {
+            @Nullable Customer customer = list.get(i);
+            if (customer != null
+                    && LastNameAscCustomerDataSource.getKeyStatic(customer).equals(key)) {
+                return i;
+            }
+        }
+        return 0; // couldn't find, fall back to 0 - could alternately search with comparator
+    }
+
+    @Override
+    public void submitList(PagedList<Customer> pagedList) {
+        super.submitList(pagedList);
+
+        if (pagedList != null) {
+            final boolean firstSet = !mSetObserved;
+            mSetObserved = true;
+
+            if (firstSet
+                    && mRecyclerView != null
+                    && (mScrollToPosition >= 0 || mScrollToKey != null)) {
+                int localScrollToPosition;
+                if (mScrollToKey != null) {
+                    localScrollToPosition = findKeyInPagedList(mScrollToKey, pagedList);
+                    mScrollToKey = null;
+                } else {
+                    // if there's 20 items unloaded items (without placeholders holding the spots)
+                    // at the beginning of list, we subtract 20 from saved position
+                    localScrollToPosition = mScrollToPosition - pagedList.getPositionOffset();
+                }
+                mRecyclerView.scrollToPosition(localScrollToPosition);
+            }
+        }
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+        mRecyclerView = recyclerView;
+    }
+
+    @Override
+    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+        mRecyclerView = null;
+    }
+}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/RoomKeyedPagedListActivity.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/RoomKeyedPagedListActivity.java
new file mode 100644
index 0000000..4f146c6
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/RoomKeyedPagedListActivity.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 androidx.room.integration.testapp;
+
+/**
+ * Sample PagedList activity which uses Room.
+ */
+public class RoomKeyedPagedListActivity extends RoomPagedListActivity {
+    @Override
+    protected boolean useKeyedQuery() {
+        return true;
+    }
+}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/RoomPagedListActivity.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/RoomPagedListActivity.java
new file mode 100644
index 0000000..b82d688
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/RoomPagedListActivity.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp;
+
+import android.os.Bundle;
+import android.widget.Button;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.paging.PagedList;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.room.integration.testapp.database.Customer;
+import androidx.room.integration.testapp.database.LastNameAscCustomerDataSource;
+
+/**
+ * Sample PagedList activity which uses Room.
+ */
+public class RoomPagedListActivity extends AppCompatActivity {
+
+    private RecyclerView mRecyclerView;
+    private PagedListCustomerAdapter mAdapter;
+
+    private static final String STRING_KEY = "STRING_KEY";
+    private static final String INT_KEY = "INT_KEY";
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_recycler_view);
+        final CustomerViewModel viewModel = ViewModelProviders.of(this)
+                .get(CustomerViewModel.class);
+
+        mRecyclerView = findViewById(R.id.recyclerview);
+        mAdapter = new PagedListCustomerAdapter();
+        mRecyclerView.setAdapter(mAdapter);
+
+        LiveData<PagedList<Customer>> livePagedList;
+        if (useKeyedQuery()) {
+            String key = null;
+            if (savedInstanceState != null) {
+                key = savedInstanceState.getString(STRING_KEY);
+                mAdapter.setScrollToKey(key);
+            }
+            livePagedList = viewModel.getLivePagedList(key);
+        } else {
+            int position = 0;
+            if (savedInstanceState != null) {
+                position = savedInstanceState.getInt(INT_KEY);
+                mAdapter.setScrollToPosition(position);
+            }
+            livePagedList = viewModel.getLivePagedList(position);
+        }
+        livePagedList.observe(this, items -> mAdapter.submitList(items));
+        final Button button = findViewById(R.id.addButton);
+        button.setOnClickListener(v -> viewModel.insertCustomer());
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        PagedList<Customer> list = mAdapter.getCurrentList();
+        if (list == null) {
+            // Can't find anything to restore
+            return;
+        }
+
+        LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
+        final int targetPosition = layoutManager.findFirstVisibleItemPosition();
+
+        if (useKeyedQuery()) {
+            Customer customer = list.get(targetPosition);
+            if (customer != null) {
+                String key = LastNameAscCustomerDataSource.getKeyStatic(customer);
+                outState.putString(STRING_KEY, key);
+            }
+        } else {
+            // NOTE: in the general case, we can't just rely on RecyclerView/LinearLayoutManager to
+            // preserve position, because of position offset which is present when using an
+            // uncounted, non-keyed source).
+            int absolutePosition = targetPosition + list.getPositionOffset();
+            outState.putInt(INT_KEY, absolutePosition);
+        }
+    }
+
+    protected boolean useKeyedQuery() {
+        return false;
+    }
+}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/Customer.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/Customer.java
new file mode 100644
index 0000000..1541114
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/Customer.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.database;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+/**
+ * Sample entity
+ */
+@Entity
+public class Customer {
+
+    @PrimaryKey(autoGenerate = true)
+    private int mId;
+
+    private String mName;
+
+    private String mLastName;
+
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        this.mId = id;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        this.mName = name;
+    }
+
+    public String getLastName() {
+        return mLastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.mLastName = lastName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Customer customer = (Customer) o;
+
+        if (mId != customer.mId) {
+            return false;
+        }
+        if (mName != null ? !mName.equals(customer.mName) : customer.mName != null) {
+            return false;
+        }
+        return mLastName != null ? mLastName.equals(customer.mLastName)
+                : customer.mLastName == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mId;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + (mLastName != null ? mLastName.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Customer{"
+                + "mId=" + mId
+                + ", mName='" + mName + '\''
+                + ", mLastName='" + mLastName + '\''
+                + '}';
+    }
+
+    public static final DiffUtil.ItemCallback<Customer> DIFF_CALLBACK =
+            new DiffUtil.ItemCallback<Customer>() {
+        @Override
+        public boolean areContentsTheSame(@NonNull Customer oldItem, @NonNull Customer newItem) {
+            return oldItem.equals(newItem);
+        }
+
+        @Override
+        public boolean areItemsTheSame(@NonNull Customer oldItem, @NonNull Customer newItem) {
+            return oldItem.getId() == newItem.getId();
+        }
+    };
+}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
new file mode 100644
index 0000000..311a593
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/CustomerDao.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.database;
+
+import androidx.paging.DataSource;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+
+import java.util.List;
+
+/**
+ * Simple Customer DAO for Room Customer list sample.
+ */
+@Dao
+public interface CustomerDao {
+
+    /**
+     * Insert a customer
+     * @param customer Customer.
+     */
+    @Insert
+    void insert(Customer customer);
+
+    /**
+     * Insert multiple customers.
+     * @param customers Customers.
+     */
+    @Insert
+    void insertAll(Customer[] customers);
+
+    /**
+     * @return DataSource.Factory of customers, ordered by last name. Use
+     * {@link androidx.paging.LivePagedListBuilder} to get a LiveData of PagedLists.
+     */
+    @Query("SELECT * FROM customer ORDER BY mLastName ASC")
+    DataSource.Factory<Integer, Customer> loadPagedAgeOrder();
+
+    /**
+     * @return number of customers
+     */
+    @Query("SELECT COUNT(*) FROM customer")
+    int countCustomers();
+
+    // Keyed
+
+    @Query("SELECT * from customer ORDER BY mLastName DESC LIMIT :limit")
+    List<Customer> customerNameInitial(int limit);
+
+    @Query("SELECT * from customer WHERE mLastName < :key ORDER BY mLastName DESC LIMIT :limit")
+    List<Customer> customerNameLoadAfter(String key, int limit);
+
+    @Query("SELECT COUNT(*) from customer WHERE mLastName < :key ORDER BY mLastName DESC")
+    int customerNameCountAfter(String key);
+
+    @Query("SELECT * from customer WHERE mLastName > :key ORDER BY mLastName ASC LIMIT :limit")
+    List<Customer> customerNameLoadBefore(String key, int limit);
+
+    @Query("SELECT COUNT(*) from customer WHERE mLastName > :key ORDER BY mLastName ASC")
+    int customerNameCountBefore(String key);
+}
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/LastNameAscCustomerDataSource.java
new file mode 100644
index 0000000..84cc8a2
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/LastNameAscCustomerDataSource.java
@@ -0,0 +1,125 @@
+/*
+ * 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 androidx.room.integration.testapp.database;
+
+import androidx.annotation.NonNull;
+import androidx.paging.DataSource;
+import androidx.paging.ItemKeyedDataSource;
+import androidx.room.InvalidationTracker;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Sample Room keyed data source.
+ */
+public class LastNameAscCustomerDataSource extends ItemKeyedDataSource<String, Customer> {
+    private final CustomerDao mCustomerDao;
+    @SuppressWarnings("FieldCanBeLocal")
+    private final InvalidationTracker.Observer mObserver;
+    private SampleDatabase mDb;
+
+    public static Factory<String, Customer> factory(final SampleDatabase db) {
+        return new Factory<String, Customer>() {
+            @Override
+            public DataSource<String, Customer> create() {
+                return new LastNameAscCustomerDataSource(db);
+            }
+        };
+    }
+
+    /**
+     * Create a DataSource from the customer table of the given database
+     */
+    private LastNameAscCustomerDataSource(SampleDatabase db) {
+        mDb = db;
+        mCustomerDao = db.getCustomerDao();
+        mObserver = new InvalidationTracker.Observer("customer") {
+            @Override
+            public void onInvalidated(@NonNull Set<String> tables) {
+                invalidate();
+            }
+        };
+        db.getInvalidationTracker().addWeakObserver(mObserver);
+    }
+
+    @Override
+    public boolean isInvalid() {
+        mDb.getInvalidationTracker().refreshVersionsSync();
+        return super.isInvalid();
+    }
+
+    @NonNull
+    public static String getKeyStatic(@NonNull Customer customer) {
+        return customer.getLastName();
+    }
+
+    @NonNull
+    @Override
+    public String getKey(@NonNull Customer customer) {
+        return getKeyStatic(customer);
+    }
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams<String> params,
+            @NonNull LoadInitialCallback<Customer> callback) {
+        String customerName = params.requestedInitialKey;
+        List<Customer> list;
+        if (customerName != null) {
+            // initial keyed load - load before 'customerName',
+            // and load after last item in before list
+            int pageSize = params.requestedLoadSize / 2;
+            String key = customerName;
+            list = mCustomerDao.customerNameLoadBefore(key, pageSize);
+            Collections.reverse(list);
+            if (!list.isEmpty()) {
+                key = getKey(list.get(list.size() - 1));
+            }
+            list.addAll(mCustomerDao.customerNameLoadAfter(key, pageSize));
+        } else {
+            list = mCustomerDao.customerNameInitial(params.requestedLoadSize);
+        }
+
+        if (params.placeholdersEnabled && !list.isEmpty()) {
+            String firstKey = getKey(list.get(0));
+            String lastKey = getKey(list.get(list.size() - 1));
+
+            // only bother counting if placeholders are desired
+            final int position = mCustomerDao.customerNameCountBefore(firstKey);
+            final int count = position + list.size() + mCustomerDao.customerNameCountAfter(lastKey);
+            callback.onResult(list, position, count);
+        } else {
+            callback.onResult(list);
+        }
+    }
+
+    @Override
+    public void loadAfter(@NonNull LoadParams<String> params,
+            @NonNull LoadCallback<Customer> callback) {
+        callback.onResult(mCustomerDao.customerNameLoadAfter(params.key, params.requestedLoadSize));
+    }
+
+    @Override
+    public void loadBefore(@NonNull LoadParams<String> params,
+            @NonNull LoadCallback<Customer> callback) {
+        List<Customer> list = mCustomerDao.customerNameLoadBefore(
+                params.key, params.requestedLoadSize);
+        Collections.reverse(list);
+        callback.onResult(list);
+    }
+}
+
diff --git a/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/SampleDatabase.java b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/SampleDatabase.java
new file mode 100644
index 0000000..e6c103d
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/androidx/room/integration/testapp/database/SampleDatabase.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 androidx.room.integration.testapp.database;
+
+import androidx.room.Database;
+import androidx.room.RoomDatabase;
+
+/**
+ * Sample database of customers.
+ */
+@Database(entities = {Customer.class},
+        version = 1, exportSchema = false)
+public abstract class SampleDatabase extends RoomDatabase {
+    /**
+     * @return customer dao.
+     */
+    public abstract CustomerDao getCustomerDao();
+}
diff --git a/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml b/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
index 34e4491..8934cbb 100644
--- a/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
+++ b/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
@@ -21,7 +21,7 @@
     android:id="@+id/activity_recycler_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context="android.arch.persistence.room.integration.testapp.RoomPagedListActivity">
+    tools:context="androidx.room.integration.testapp.RoomPagedListActivity">
     <android.support.v7.widget.RecyclerView
         android:id="@+id/recyclerview"
         android:layout_width="match_parent"
diff --git a/room/migration/api/1.0.0.txt b/room/migration/api_legacy/1.0.0.txt
similarity index 100%
rename from room/migration/api/1.0.0.txt
rename to room/migration/api_legacy/1.0.0.txt
diff --git a/room/migration/build.gradle b/room/migration/build.gradle
index 2b683e8..715fb62 100644
--- a/room/migration/build.gradle
+++ b/room/migration/build.gradle
@@ -27,7 +27,7 @@
     test.java.srcDirs += 'src/tests/kotlin'
 }
 dependencies {
-    compile(project(":room:common"))
+    compile(project(":room:room-common"))
     compile(KOTLIN_STDLIB)
     compile(GSON)
     testCompile(JUNIT)
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/BundleUtil.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/BundleUtil.java
deleted file mode 100644
index 4356e69..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/BundleUtil.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-/**
- * Utility functions for bundling.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class BundleUtil {
-    /**
-     * Placeholder for table names in queries.
-     */
-    public static final String TABLE_NAME_PLACEHOLDER = "${TABLE_NAME}";
-
-    static String replaceTableName(String contents, String tableName) {
-        return contents.replace(TABLE_NAME_PLACEHOLDER, tableName);
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/DatabaseBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/DatabaseBundle.java
deleted file mode 100644
index f131838..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/DatabaseBundle.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Data class that holds the schema information for a
- * {@link android.arch.persistence.room.Database Database}.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class DatabaseBundle implements SchemaEquality<DatabaseBundle> {
-    @SerializedName("version")
-    private int mVersion;
-    @SerializedName("identityHash")
-    private String mIdentityHash;
-    @SerializedName("entities")
-    private List<EntityBundle> mEntities;
-    // then entity where we keep room information
-    @SerializedName("setupQueries")
-    private List<String> mSetupQueries;
-    private transient Map<String, EntityBundle> mEntitiesByTableName;
-
-    /**
-     * Creates a new database
-     * @param version Version
-     * @param identityHash Identity hash
-     * @param entities List of entities
-     */
-    public DatabaseBundle(int version, String identityHash, List<EntityBundle> entities,
-            List<String> setupQueries) {
-        mVersion = version;
-        mIdentityHash = identityHash;
-        mEntities = entities;
-        mSetupQueries = setupQueries;
-    }
-
-    /**
-     * @return The identity has of the Database.
-     */
-    public String getIdentityHash() {
-        return mIdentityHash;
-    }
-
-    /**
-     * @return The database version.
-     */
-    public int getVersion() {
-        return mVersion;
-    }
-
-    /**
-     * @return List of entities.
-     */
-    public List<EntityBundle> getEntities() {
-        return mEntities;
-    }
-
-    /**
-     * @return Map of entities, keyed by table name.
-     */
-    @SuppressWarnings("unused")
-    public Map<String, EntityBundle> getEntitiesByTableName() {
-        if (mEntitiesByTableName == null) {
-            mEntitiesByTableName = new HashMap<>();
-            for (EntityBundle bundle : mEntities) {
-                mEntitiesByTableName.put(bundle.getTableName(), bundle);
-            }
-        }
-        return mEntitiesByTableName;
-    }
-
-    /**
-     * @return List of SQL queries to build this database from scratch.
-     */
-    public List<String> buildCreateQueries() {
-        List<String> result = new ArrayList<>();
-        for (EntityBundle entityBundle : mEntities) {
-            result.addAll(entityBundle.buildCreateQueries());
-        }
-        result.addAll(mSetupQueries);
-        return result;
-    }
-
-    @Override
-    public boolean isSchemaEqual(DatabaseBundle other) {
-        return SchemaEqualityUtil.checkSchemaEquality(getEntitiesByTableName(),
-                other.getEntitiesByTableName());
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/EntityBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/EntityBundle.java
deleted file mode 100644
index d78ac35..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/EntityBundle.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import static android.arch.persistence.room.migration.bundle.SchemaEqualityUtil.checkSchemaEquality;
-
-import android.support.annotation.RestrictTo;
-
-import com.google.gson.annotations.SerializedName;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Data class that holds the schema information about an
- * {@link android.arch.persistence.room.Entity Entity}.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class EntityBundle implements SchemaEquality<EntityBundle> {
-
-    static final String NEW_TABLE_PREFIX = "_new_";
-
-    @SerializedName("tableName")
-    private String mTableName;
-    @SerializedName("createSql")
-    private String mCreateSql;
-    @SerializedName("fields")
-    private List<FieldBundle> mFields;
-    @SerializedName("primaryKey")
-    private PrimaryKeyBundle mPrimaryKey;
-    @SerializedName("indices")
-    private List<IndexBundle> mIndices;
-    @SerializedName("foreignKeys")
-    private List<ForeignKeyBundle> mForeignKeys;
-
-    private transient String mNewTableName;
-    private transient Map<String, FieldBundle> mFieldsByColumnName;
-
-    /**
-     * Creates a new bundle.
-     *
-     * @param tableName The table name.
-     * @param createSql Create query with the table name placeholder.
-     * @param fields The list of fields.
-     * @param primaryKey The primary key.
-     * @param indices The list of indices
-     * @param foreignKeys The list of foreign keys
-     */
-    public EntityBundle(String tableName, String createSql,
-            List<FieldBundle> fields,
-            PrimaryKeyBundle primaryKey,
-            List<IndexBundle> indices,
-            List<ForeignKeyBundle> foreignKeys) {
-        mTableName = tableName;
-        mCreateSql = createSql;
-        mFields = fields;
-        mPrimaryKey = primaryKey;
-        mIndices = indices;
-        mForeignKeys = foreignKeys;
-    }
-
-    /**
-     * @return The table name if it is created during a table schema modification.
-     */
-    public String getNewTableName() {
-        if (mNewTableName == null) {
-            mNewTableName = NEW_TABLE_PREFIX + mTableName;
-        }
-        return mNewTableName;
-    }
-
-    /**
-     * @return Map of fields keyed by their column names.
-     */
-    public Map<String, FieldBundle> getFieldsByColumnName() {
-        if (mFieldsByColumnName == null) {
-            mFieldsByColumnName = new HashMap<>();
-            for (FieldBundle bundle : mFields) {
-                mFieldsByColumnName.put(bundle.getColumnName(), bundle);
-            }
-        }
-        return mFieldsByColumnName;
-    }
-
-    /**
-     * @return The table name.
-     */
-    public String getTableName() {
-        return mTableName;
-    }
-
-    /**
-     * @return The create query with table name placeholder.
-     */
-    public String getCreateSql() {
-        return mCreateSql;
-    }
-
-    /**
-     * @return List of fields.
-     */
-    public List<FieldBundle> getFields() {
-        return mFields;
-    }
-
-    /**
-     * @return The primary key description.
-     */
-    public PrimaryKeyBundle getPrimaryKey() {
-        return mPrimaryKey;
-    }
-
-    /**
-     * @return List of indices.
-     */
-    public List<IndexBundle> getIndices() {
-        return mIndices;
-    }
-
-    /**
-     * @return List of foreign keys.
-     */
-    public List<ForeignKeyBundle> getForeignKeys() {
-        return mForeignKeys;
-    }
-
-    /**
-     * @return Create table SQL query that uses the actual table name.
-     */
-    public String createTable() {
-        return BundleUtil.replaceTableName(mCreateSql, getTableName());
-    }
-
-    /**
-     * @return Create table SQL query that uses the table name with "new" prefix.
-     */
-    public String createNewTable() {
-        return BundleUtil.replaceTableName(mCreateSql, getNewTableName());
-    }
-
-    /**
-     * @return Renames the table with {@link #getNewTableName()} to {@link #getTableName()}.
-     */
-    @NotNull
-    public String renameToOriginal() {
-        return "ALTER TABLE " + getNewTableName() + " RENAME TO " + getTableName();
-    }
-
-    /**
-     * @return Creates the list of SQL queries that are necessary to create this entity.
-     */
-    public Collection<String> buildCreateQueries() {
-        List<String> result = new ArrayList<>();
-        result.add(createTable());
-        for (IndexBundle indexBundle : mIndices) {
-            result.add(indexBundle.create(getTableName()));
-        }
-        return result;
-    }
-
-    @Override
-    public boolean isSchemaEqual(EntityBundle other) {
-        if (!mTableName.equals(other.mTableName)) {
-            return false;
-        }
-        return checkSchemaEquality(getFieldsByColumnName(), other.getFieldsByColumnName())
-                && checkSchemaEquality(mPrimaryKey, other.mPrimaryKey)
-                && checkSchemaEquality(mIndices, other.mIndices)
-                && checkSchemaEquality(mForeignKeys, other.mForeignKeys);
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/FieldBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/FieldBundle.java
deleted file mode 100644
index 5f74087..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/FieldBundle.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * Data class that holds the schema information for an
- * {@link android.arch.persistence.room.Entity Entity} field.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class FieldBundle implements SchemaEquality<FieldBundle> {
-    @SerializedName("fieldPath")
-    private String mFieldPath;
-    @SerializedName("columnName")
-    private String mColumnName;
-    @SerializedName("affinity")
-    private String mAffinity;
-    @SerializedName("notNull")
-    private boolean mNonNull;
-
-    public FieldBundle(String fieldPath, String columnName, String affinity, boolean nonNull) {
-        mFieldPath = fieldPath;
-        mColumnName = columnName;
-        mAffinity = affinity;
-        mNonNull = nonNull;
-    }
-
-    public String getFieldPath() {
-        return mFieldPath;
-    }
-
-    public String getColumnName() {
-        return mColumnName;
-    }
-
-    public String getAffinity() {
-        return mAffinity;
-    }
-
-    public boolean isNonNull() {
-        return mNonNull;
-    }
-
-    @Override
-    public boolean isSchemaEqual(FieldBundle other) {
-        if (mNonNull != other.mNonNull) return false;
-        if (mColumnName != null ? !mColumnName.equals(other.mColumnName)
-                : other.mColumnName != null) {
-            return false;
-        }
-        return mAffinity != null ? mAffinity.equals(other.mAffinity) : other.mAffinity == null;
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
deleted file mode 100644
index 367dd74..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundle.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.util.List;
-
-/**
- * Holds the information about a foreign key reference.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ForeignKeyBundle implements SchemaEquality<ForeignKeyBundle> {
-    @SerializedName("table")
-    private String mTable;
-    @SerializedName("onDelete")
-    private String mOnDelete;
-    @SerializedName("onUpdate")
-    private String mOnUpdate;
-    @SerializedName("columns")
-    private List<String> mColumns;
-    @SerializedName("referencedColumns")
-    private List<String> mReferencedColumns;
-
-    /**
-     * Creates a foreign key bundle with the given parameters.
-     *
-     * @param table             The target table
-     * @param onDelete          OnDelete action
-     * @param onUpdate          OnUpdate action
-     * @param columns           The list of columns in the current table
-     * @param referencedColumns The list of columns in the referenced table
-     */
-    public ForeignKeyBundle(String table, String onDelete, String onUpdate,
-            List<String> columns, List<String> referencedColumns) {
-        mTable = table;
-        mOnDelete = onDelete;
-        mOnUpdate = onUpdate;
-        mColumns = columns;
-        mReferencedColumns = referencedColumns;
-    }
-
-    /**
-     * Returns the table name
-     *
-     * @return Returns the table name
-     */
-    public String getTable() {
-        return mTable;
-    }
-
-    /**
-     * Returns the SQLite foreign key action that will be performed when referenced row is deleted.
-     *
-     * @return The SQLite on delete action
-     */
-    public String getOnDelete() {
-        return mOnDelete;
-    }
-
-    /**
-     * Returns the SQLite foreign key action that will be performed when referenced row is updated.
-     *
-     * @return The SQLite on update action
-     */
-    public String getOnUpdate() {
-        return mOnUpdate;
-    }
-
-    /**
-     * Returns the ordered list of columns in the current table.
-     *
-     * @return The list of columns in the current entity.
-     */
-    public List<String> getColumns() {
-        return mColumns;
-    }
-
-    /**
-     * Returns the ordered list of columns in the referenced table.
-     *
-     * @return The list of columns in the referenced entity.
-     */
-    public List<String> getReferencedColumns() {
-        return mReferencedColumns;
-    }
-
-    @Override
-    public boolean isSchemaEqual(ForeignKeyBundle other) {
-        if (mTable != null ? !mTable.equals(other.mTable) : other.mTable != null) return false;
-        if (mOnDelete != null ? !mOnDelete.equals(other.mOnDelete) : other.mOnDelete != null) {
-            return false;
-        }
-        if (mOnUpdate != null ? !mOnUpdate.equals(other.mOnUpdate) : other.mOnUpdate != null) {
-            return false;
-        }
-        // order matters
-        return mColumns.equals(other.mColumns) && mReferencedColumns.equals(
-                other.mReferencedColumns);
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/IndexBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/IndexBundle.java
deleted file mode 100644
index e991316..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/IndexBundle.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.util.List;
-
-/**
- * Data class that holds the schema information about a table Index.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class IndexBundle implements SchemaEquality<IndexBundle> {
-    // should match Index.kt
-    public static final String DEFAULT_PREFIX = "index_";
-    @SerializedName("name")
-    private String mName;
-    @SerializedName("unique")
-    private boolean mUnique;
-    @SerializedName("columnNames")
-    private List<String> mColumnNames;
-    @SerializedName("createSql")
-    private String mCreateSql;
-
-    public IndexBundle(String name, boolean unique, List<String> columnNames,
-            String createSql) {
-        mName = name;
-        mUnique = unique;
-        mColumnNames = columnNames;
-        mCreateSql = createSql;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public boolean isUnique() {
-        return mUnique;
-    }
-
-    public List<String> getColumnNames() {
-        return mColumnNames;
-    }
-
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public String create(String tableName) {
-        return BundleUtil.replaceTableName(mCreateSql, tableName);
-    }
-
-    @Override
-    public boolean isSchemaEqual(IndexBundle other) {
-        if (mUnique != other.mUnique) return false;
-        if (mName.startsWith(DEFAULT_PREFIX)) {
-            if (!other.mName.startsWith(DEFAULT_PREFIX)) {
-                return false;
-            }
-        } else if (other.mName.startsWith(DEFAULT_PREFIX)) {
-            return false;
-        } else if (!mName.equals(other.mName)) {
-            return false;
-        }
-
-        // order matters
-        if (mColumnNames != null ? !mColumnNames.equals(other.mColumnNames)
-                : other.mColumnNames != null) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundle.java
deleted file mode 100644
index 820aa7e..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundle.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.util.List;
-
-/**
- * Data class that holds the schema information about a primary key.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class PrimaryKeyBundle implements SchemaEquality<PrimaryKeyBundle> {
-    @SerializedName("columnNames")
-    private List<String> mColumnNames;
-    @SerializedName("autoGenerate")
-    private boolean mAutoGenerate;
-
-    public PrimaryKeyBundle(boolean autoGenerate, List<String> columnNames) {
-        mColumnNames = columnNames;
-        mAutoGenerate = autoGenerate;
-    }
-
-    public List<String> getColumnNames() {
-        return mColumnNames;
-    }
-
-    public boolean isAutoGenerate() {
-        return mAutoGenerate;
-    }
-
-    @Override
-    public boolean isSchemaEqual(PrimaryKeyBundle other) {
-        return mColumnNames.equals(other.mColumnNames) && mAutoGenerate == other.mAutoGenerate;
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaBundle.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaBundle.java
deleted file mode 100644
index af35e6f..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaBundle.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.annotations.SerializedName;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Data class that holds the information about a database schema export.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class SchemaBundle implements SchemaEquality<SchemaBundle> {
-
-    @SerializedName("formatVersion")
-    private int mFormatVersion;
-    @SerializedName("database")
-    private DatabaseBundle mDatabase;
-
-    private static final Gson GSON;
-    private static final String CHARSET = "UTF-8";
-    public static final int LATEST_FORMAT = 1;
-
-    static {
-        GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
-    }
-
-    public SchemaBundle(int formatVersion, DatabaseBundle database) {
-        mFormatVersion = formatVersion;
-        mDatabase = database;
-    }
-
-    @SuppressWarnings("unused")
-    public int getFormatVersion() {
-        return mFormatVersion;
-    }
-
-    public DatabaseBundle getDatabase() {
-        return mDatabase;
-    }
-
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static SchemaBundle deserialize(InputStream fis)
-            throws UnsupportedEncodingException {
-        InputStreamReader is = new InputStreamReader(fis, CHARSET);
-        try {
-            return GSON.fromJson(is, SchemaBundle.class);
-        } finally {
-            safeClose(is);
-            safeClose(fis);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static void serialize(SchemaBundle bundle, File file) throws IOException {
-        FileOutputStream fos = new FileOutputStream(file, false);
-        OutputStreamWriter osw = new OutputStreamWriter(fos, CHARSET);
-        try {
-            GSON.toJson(bundle, osw);
-        } finally {
-            safeClose(osw);
-            safeClose(fos);
-        }
-    }
-
-    private static void safeClose(Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (Throwable ignored) {
-            }
-        }
-    }
-
-    @Override
-    public boolean isSchemaEqual(SchemaBundle other) {
-        return SchemaEqualityUtil.checkSchemaEquality(mDatabase, other.mDatabase)
-                && mFormatVersion == other.mFormatVersion;
-    }
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaEquality.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaEquality.java
deleted file mode 100644
index 59ea4b0..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaEquality.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 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.migration.bundle;
-
-import android.support.annotation.RestrictTo;
-
-/**
- * A loose equals check which checks schema equality instead of 100% equality (e.g. order of
- * columns in an entity does not have to match)
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-interface SchemaEquality<T> {
-    boolean isSchemaEqual(T other);
-}
diff --git a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaEqualityUtil.java b/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaEqualityUtil.java
deleted file mode 100644
index 65a7572..0000000
--- a/room/migration/src/main/java/android/arch/persistence/room/migration/bundle/SchemaEqualityUtil.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 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.migration.bundle;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * utility class to run schema equality on collections.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-class SchemaEqualityUtil {
-    static <T, K extends SchemaEquality<K>> boolean checkSchemaEquality(
-            @Nullable Map<T, K> map1, @Nullable Map<T, K> map2) {
-        if (map1 == null) {
-            return map2 == null;
-        }
-        if (map2 == null) {
-            return false;
-        }
-        if (map1.size() != map2.size()) {
-            return false;
-        }
-        for (Map.Entry<T, K> pair : map1.entrySet()) {
-            if (!checkSchemaEquality(pair.getValue(), map2.get(pair.getKey()))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    static <K extends SchemaEquality<K>> boolean checkSchemaEquality(
-            @Nullable List<K> list1, @Nullable List<K> list2) {
-        if (list1 == null) {
-            return list2 == null;
-        }
-        if (list2 == null) {
-            return false;
-        }
-        if (list1.size() != list2.size()) {
-            return false;
-        }
-        // we don't care this is n^2, small list + only used for testing.
-        for (K item1 : list1) {
-            // find matching item
-            boolean matched = false;
-            for (K item2 : list2) {
-                if (checkSchemaEquality(item1, item2)) {
-                    matched = true;
-                    break;
-                }
-            }
-            if (!matched) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @SuppressWarnings("SimplifiableIfStatement")
-    static <K extends SchemaEquality<K>> boolean checkSchemaEquality(
-            @Nullable K item1, @Nullable K item2) {
-        if (item1 == null) {
-            return item2 == null;
-        }
-        if (item2 == null) {
-            return false;
-        }
-        return item1.isSchemaEqual(item2);
-    }
-}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/BundleUtil.java b/room/migration/src/main/java/androidx/room/migration/bundle/BundleUtil.java
new file mode 100644
index 0000000..8eb4d10
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/BundleUtil.java
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Utility functions for bundling.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class BundleUtil {
+    /**
+     * Placeholder for table names in queries.
+     */
+    public static final String TABLE_NAME_PLACEHOLDER = "${TABLE_NAME}";
+
+    static String replaceTableName(String contents, String tableName) {
+        return contents.replace(TABLE_NAME_PLACEHOLDER, tableName);
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/DatabaseBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/DatabaseBundle.java
new file mode 100644
index 0000000..fa658bb
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/DatabaseBundle.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data class that holds the schema information for a
+ * {@link androidx.room.Database Database}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class DatabaseBundle implements SchemaEquality<DatabaseBundle> {
+    @SerializedName("version")
+    private int mVersion;
+    @SerializedName("identityHash")
+    private String mIdentityHash;
+    @SerializedName("entities")
+    private List<EntityBundle> mEntities;
+    // then entity where we keep room information
+    @SerializedName("setupQueries")
+    private List<String> mSetupQueries;
+    private transient Map<String, EntityBundle> mEntitiesByTableName;
+
+    /**
+     * Creates a new database
+     * @param version Version
+     * @param identityHash Identity hash
+     * @param entities List of entities
+     */
+    public DatabaseBundle(int version, String identityHash, List<EntityBundle> entities,
+            List<String> setupQueries) {
+        mVersion = version;
+        mIdentityHash = identityHash;
+        mEntities = entities;
+        mSetupQueries = setupQueries;
+    }
+
+    /**
+     * @return The identity has of the Database.
+     */
+    public String getIdentityHash() {
+        return mIdentityHash;
+    }
+
+    /**
+     * @return The database version.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * @return List of entities.
+     */
+    public List<EntityBundle> getEntities() {
+        return mEntities;
+    }
+
+    /**
+     * @return Map of entities, keyed by table name.
+     */
+    @SuppressWarnings("unused")
+    public Map<String, EntityBundle> getEntitiesByTableName() {
+        if (mEntitiesByTableName == null) {
+            mEntitiesByTableName = new HashMap<>();
+            for (EntityBundle bundle : mEntities) {
+                mEntitiesByTableName.put(bundle.getTableName(), bundle);
+            }
+        }
+        return mEntitiesByTableName;
+    }
+
+    /**
+     * @return List of SQL queries to build this database from scratch.
+     */
+    public List<String> buildCreateQueries() {
+        List<String> result = new ArrayList<>();
+        for (EntityBundle entityBundle : mEntities) {
+            result.addAll(entityBundle.buildCreateQueries());
+        }
+        result.addAll(mSetupQueries);
+        return result;
+    }
+
+    @Override
+    public boolean isSchemaEqual(DatabaseBundle other) {
+        return SchemaEqualityUtil.checkSchemaEquality(getEntitiesByTableName(),
+                other.getEntitiesByTableName());
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/EntityBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/EntityBundle.java
new file mode 100644
index 0000000..7fd836f
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/EntityBundle.java
@@ -0,0 +1,192 @@
+/*
+ * 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 androidx.room.migration.bundle;
+
+import static androidx.room.migration.bundle.SchemaEqualityUtil.checkSchemaEquality;
+
+import androidx.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Data class that holds the schema information about an
+ * {@link androidx.room.Entity Entity}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class EntityBundle implements SchemaEquality<EntityBundle> {
+
+    static final String NEW_TABLE_PREFIX = "_new_";
+
+    @SerializedName("tableName")
+    private String mTableName;
+    @SerializedName("createSql")
+    private String mCreateSql;
+    @SerializedName("fields")
+    private List<FieldBundle> mFields;
+    @SerializedName("primaryKey")
+    private PrimaryKeyBundle mPrimaryKey;
+    @SerializedName("indices")
+    private List<IndexBundle> mIndices;
+    @SerializedName("foreignKeys")
+    private List<ForeignKeyBundle> mForeignKeys;
+
+    private transient String mNewTableName;
+    private transient Map<String, FieldBundle> mFieldsByColumnName;
+
+    /**
+     * Creates a new bundle.
+     *
+     * @param tableName The table name.
+     * @param createSql Create query with the table name placeholder.
+     * @param fields The list of fields.
+     * @param primaryKey The primary key.
+     * @param indices The list of indices
+     * @param foreignKeys The list of foreign keys
+     */
+    public EntityBundle(String tableName, String createSql,
+            List<FieldBundle> fields,
+            PrimaryKeyBundle primaryKey,
+            List<IndexBundle> indices,
+            List<ForeignKeyBundle> foreignKeys) {
+        mTableName = tableName;
+        mCreateSql = createSql;
+        mFields = fields;
+        mPrimaryKey = primaryKey;
+        mIndices = indices;
+        mForeignKeys = foreignKeys;
+    }
+
+    /**
+     * @return The table name if it is created during a table schema modification.
+     */
+    public String getNewTableName() {
+        if (mNewTableName == null) {
+            mNewTableName = NEW_TABLE_PREFIX + mTableName;
+        }
+        return mNewTableName;
+    }
+
+    /**
+     * @return Map of fields keyed by their column names.
+     */
+    public Map<String, FieldBundle> getFieldsByColumnName() {
+        if (mFieldsByColumnName == null) {
+            mFieldsByColumnName = new HashMap<>();
+            for (FieldBundle bundle : mFields) {
+                mFieldsByColumnName.put(bundle.getColumnName(), bundle);
+            }
+        }
+        return mFieldsByColumnName;
+    }
+
+    /**
+     * @return The table name.
+     */
+    public String getTableName() {
+        return mTableName;
+    }
+
+    /**
+     * @return The create query with table name placeholder.
+     */
+    public String getCreateSql() {
+        return mCreateSql;
+    }
+
+    /**
+     * @return List of fields.
+     */
+    public List<FieldBundle> getFields() {
+        return mFields;
+    }
+
+    /**
+     * @return The primary key description.
+     */
+    public PrimaryKeyBundle getPrimaryKey() {
+        return mPrimaryKey;
+    }
+
+    /**
+     * @return List of indices.
+     */
+    public List<IndexBundle> getIndices() {
+        return mIndices;
+    }
+
+    /**
+     * @return List of foreign keys.
+     */
+    public List<ForeignKeyBundle> getForeignKeys() {
+        return mForeignKeys;
+    }
+
+    /**
+     * @return Create table SQL query that uses the actual table name.
+     */
+    public String createTable() {
+        return BundleUtil.replaceTableName(mCreateSql, getTableName());
+    }
+
+    /**
+     * @return Create table SQL query that uses the table name with "new" prefix.
+     */
+    public String createNewTable() {
+        return BundleUtil.replaceTableName(mCreateSql, getNewTableName());
+    }
+
+    /**
+     * @return Renames the table with {@link #getNewTableName()} to {@link #getTableName()}.
+     */
+    @NotNull
+    public String renameToOriginal() {
+        return "ALTER TABLE " + getNewTableName() + " RENAME TO " + getTableName();
+    }
+
+    /**
+     * @return Creates the list of SQL queries that are necessary to create this entity.
+     */
+    public Collection<String> buildCreateQueries() {
+        List<String> result = new ArrayList<>();
+        result.add(createTable());
+        for (IndexBundle indexBundle : mIndices) {
+            result.add(indexBundle.create(getTableName()));
+        }
+        return result;
+    }
+
+    @Override
+    public boolean isSchemaEqual(EntityBundle other) {
+        if (!mTableName.equals(other.mTableName)) {
+            return false;
+        }
+        return checkSchemaEquality(getFieldsByColumnName(), other.getFieldsByColumnName())
+                && checkSchemaEquality(mPrimaryKey, other.mPrimaryKey)
+                && checkSchemaEquality(mIndices, other.mIndices)
+                && checkSchemaEquality(mForeignKeys, other.mForeignKeys);
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/FieldBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/FieldBundle.java
new file mode 100644
index 0000000..c6612f4
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/FieldBundle.java
@@ -0,0 +1,72 @@
+/*
+ * 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 androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Data class that holds the schema information for an
+ * {@link androidx.room.Entity Entity} field.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FieldBundle implements SchemaEquality<FieldBundle> {
+    @SerializedName("fieldPath")
+    private String mFieldPath;
+    @SerializedName("columnName")
+    private String mColumnName;
+    @SerializedName("affinity")
+    private String mAffinity;
+    @SerializedName("notNull")
+    private boolean mNonNull;
+
+    public FieldBundle(String fieldPath, String columnName, String affinity, boolean nonNull) {
+        mFieldPath = fieldPath;
+        mColumnName = columnName;
+        mAffinity = affinity;
+        mNonNull = nonNull;
+    }
+
+    public String getFieldPath() {
+        return mFieldPath;
+    }
+
+    public String getColumnName() {
+        return mColumnName;
+    }
+
+    public String getAffinity() {
+        return mAffinity;
+    }
+
+    public boolean isNonNull() {
+        return mNonNull;
+    }
+
+    @Override
+    public boolean isSchemaEqual(FieldBundle other) {
+        if (mNonNull != other.mNonNull) return false;
+        if (mColumnName != null ? !mColumnName.equals(other.mColumnName)
+                : other.mColumnName != null) {
+            return false;
+        }
+        return mAffinity != null ? mAffinity.equals(other.mAffinity) : other.mAffinity == null;
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/ForeignKeyBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/ForeignKeyBundle.java
new file mode 100644
index 0000000..3d45900
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/ForeignKeyBundle.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * Holds the information about a foreign key reference.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ForeignKeyBundle implements SchemaEquality<ForeignKeyBundle> {
+    @SerializedName("table")
+    private String mTable;
+    @SerializedName("onDelete")
+    private String mOnDelete;
+    @SerializedName("onUpdate")
+    private String mOnUpdate;
+    @SerializedName("columns")
+    private List<String> mColumns;
+    @SerializedName("referencedColumns")
+    private List<String> mReferencedColumns;
+
+    /**
+     * Creates a foreign key bundle with the given parameters.
+     *
+     * @param table             The target table
+     * @param onDelete          OnDelete action
+     * @param onUpdate          OnUpdate action
+     * @param columns           The list of columns in the current table
+     * @param referencedColumns The list of columns in the referenced table
+     */
+    public ForeignKeyBundle(String table, String onDelete, String onUpdate,
+            List<String> columns, List<String> referencedColumns) {
+        mTable = table;
+        mOnDelete = onDelete;
+        mOnUpdate = onUpdate;
+        mColumns = columns;
+        mReferencedColumns = referencedColumns;
+    }
+
+    /**
+     * Returns the table name
+     *
+     * @return Returns the table name
+     */
+    public String getTable() {
+        return mTable;
+    }
+
+    /**
+     * Returns the SQLite foreign key action that will be performed when referenced row is deleted.
+     *
+     * @return The SQLite on delete action
+     */
+    public String getOnDelete() {
+        return mOnDelete;
+    }
+
+    /**
+     * Returns the SQLite foreign key action that will be performed when referenced row is updated.
+     *
+     * @return The SQLite on update action
+     */
+    public String getOnUpdate() {
+        return mOnUpdate;
+    }
+
+    /**
+     * Returns the ordered list of columns in the current table.
+     *
+     * @return The list of columns in the current entity.
+     */
+    public List<String> getColumns() {
+        return mColumns;
+    }
+
+    /**
+     * Returns the ordered list of columns in the referenced table.
+     *
+     * @return The list of columns in the referenced entity.
+     */
+    public List<String> getReferencedColumns() {
+        return mReferencedColumns;
+    }
+
+    @Override
+    public boolean isSchemaEqual(ForeignKeyBundle other) {
+        if (mTable != null ? !mTable.equals(other.mTable) : other.mTable != null) return false;
+        if (mOnDelete != null ? !mOnDelete.equals(other.mOnDelete) : other.mOnDelete != null) {
+            return false;
+        }
+        if (mOnUpdate != null ? !mOnUpdate.equals(other.mOnUpdate) : other.mOnUpdate != null) {
+            return false;
+        }
+        // order matters
+        return mColumns.equals(other.mColumns) && mReferencedColumns.equals(
+                other.mReferencedColumns);
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
new file mode 100644
index 0000000..baf15ab
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
@@ -0,0 +1,91 @@
+/*
+ * 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 androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * Data class that holds the schema information about a table Index.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class IndexBundle implements SchemaEquality<IndexBundle> {
+    // should match Index.kt
+    public static final String DEFAULT_PREFIX = "index_";
+    @SerializedName("name")
+    private String mName;
+    @SerializedName("unique")
+    private boolean mUnique;
+    @SerializedName("columnNames")
+    private List<String> mColumnNames;
+    @SerializedName("createSql")
+    private String mCreateSql;
+
+    public IndexBundle(String name, boolean unique, List<String> columnNames,
+            String createSql) {
+        mName = name;
+        mUnique = unique;
+        mColumnNames = columnNames;
+        mCreateSql = createSql;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public boolean isUnique() {
+        return mUnique;
+    }
+
+    public List<String> getColumnNames() {
+        return mColumnNames;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public String create(String tableName) {
+        return BundleUtil.replaceTableName(mCreateSql, tableName);
+    }
+
+    @Override
+    public boolean isSchemaEqual(IndexBundle other) {
+        if (mUnique != other.mUnique) return false;
+        if (mName.startsWith(DEFAULT_PREFIX)) {
+            if (!other.mName.startsWith(DEFAULT_PREFIX)) {
+                return false;
+            }
+        } else if (other.mName.startsWith(DEFAULT_PREFIX)) {
+            return false;
+        } else if (!mName.equals(other.mName)) {
+            return false;
+        }
+
+        // order matters
+        if (mColumnNames != null ? !mColumnNames.equals(other.mColumnNames)
+                : other.mColumnNames != null) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/PrimaryKeyBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/PrimaryKeyBundle.java
new file mode 100644
index 0000000..a77da7d
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/PrimaryKeyBundle.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+/**
+ * Data class that holds the schema information about a primary key.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class PrimaryKeyBundle implements SchemaEquality<PrimaryKeyBundle> {
+    @SerializedName("columnNames")
+    private List<String> mColumnNames;
+    @SerializedName("autoGenerate")
+    private boolean mAutoGenerate;
+
+    public PrimaryKeyBundle(boolean autoGenerate, List<String> columnNames) {
+        mColumnNames = columnNames;
+        mAutoGenerate = autoGenerate;
+    }
+
+    public List<String> getColumnNames() {
+        return mColumnNames;
+    }
+
+    public boolean isAutoGenerate() {
+        return mAutoGenerate;
+    }
+
+    @Override
+    public boolean isSchemaEqual(PrimaryKeyBundle other) {
+        return mColumnNames.equals(other.mColumnNames) && mAutoGenerate == other.mAutoGenerate;
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.java
new file mode 100644
index 0000000..8bc3d3d
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Data class that holds the information about a database schema export.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SchemaBundle implements SchemaEquality<SchemaBundle> {
+
+    @SerializedName("formatVersion")
+    private int mFormatVersion;
+    @SerializedName("database")
+    private DatabaseBundle mDatabase;
+
+    private static final Gson GSON;
+    private static final String CHARSET = "UTF-8";
+    public static final int LATEST_FORMAT = 1;
+
+    static {
+        GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+    }
+
+    public SchemaBundle(int formatVersion, DatabaseBundle database) {
+        mFormatVersion = formatVersion;
+        mDatabase = database;
+    }
+
+    @SuppressWarnings("unused")
+    public int getFormatVersion() {
+        return mFormatVersion;
+    }
+
+    public DatabaseBundle getDatabase() {
+        return mDatabase;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static SchemaBundle deserialize(InputStream fis)
+            throws UnsupportedEncodingException {
+        InputStreamReader is = new InputStreamReader(fis, CHARSET);
+        try {
+            return GSON.fromJson(is, SchemaBundle.class);
+        } finally {
+            safeClose(is);
+            safeClose(fis);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static void serialize(SchemaBundle bundle, File file) throws IOException {
+        FileOutputStream fos = new FileOutputStream(file, false);
+        OutputStreamWriter osw = new OutputStreamWriter(fos, CHARSET);
+        try {
+            GSON.toJson(bundle, osw);
+        } finally {
+            safeClose(osw);
+            safeClose(fos);
+        }
+    }
+
+    private static void safeClose(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (Throwable ignored) {
+            }
+        }
+    }
+
+    @Override
+    public boolean isSchemaEqual(SchemaBundle other) {
+        return SchemaEqualityUtil.checkSchemaEquality(mDatabase, other.mDatabase)
+                && mFormatVersion == other.mFormatVersion;
+    }
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/SchemaEquality.java b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaEquality.java
new file mode 100644
index 0000000..d300fea
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaEquality.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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 androidx.room.migration.bundle;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * A loose equals check which checks schema equality instead of 100% equality (e.g. order of
+ * columns in an entity does not have to match)
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+interface SchemaEquality<T> {
+    boolean isSchemaEqual(T other);
+}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/SchemaEqualityUtil.java b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaEqualityUtil.java
new file mode 100644
index 0000000..799c3d8
--- /dev/null
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/SchemaEqualityUtil.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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 androidx.room.migration.bundle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * utility class to run schema equality on collections.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class SchemaEqualityUtil {
+    static <T, K extends SchemaEquality<K>> boolean checkSchemaEquality(
+            @Nullable Map<T, K> map1, @Nullable Map<T, K> map2) {
+        if (map1 == null) {
+            return map2 == null;
+        }
+        if (map2 == null) {
+            return false;
+        }
+        if (map1.size() != map2.size()) {
+            return false;
+        }
+        for (Map.Entry<T, K> pair : map1.entrySet()) {
+            if (!checkSchemaEquality(pair.getValue(), map2.get(pair.getKey()))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static <K extends SchemaEquality<K>> boolean checkSchemaEquality(
+            @Nullable List<K> list1, @Nullable List<K> list2) {
+        if (list1 == null) {
+            return list2 == null;
+        }
+        if (list2 == null) {
+            return false;
+        }
+        if (list1.size() != list2.size()) {
+            return false;
+        }
+        // we don't care this is n^2, small list + only used for testing.
+        for (K item1 : list1) {
+            // find matching item
+            boolean matched = false;
+            for (K item2 : list2) {
+                if (checkSchemaEquality(item1, item2)) {
+                    matched = true;
+                    break;
+                }
+            }
+            if (!matched) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @SuppressWarnings("SimplifiableIfStatement")
+    static <K extends SchemaEquality<K>> boolean checkSchemaEquality(
+            @Nullable K item1, @Nullable K item2) {
+        if (item1 == null) {
+            return item2 == null;
+        }
+        if (item2 == null) {
+            return false;
+        }
+        return item1.isSchemaEqual(item2);
+    }
+}
diff --git a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/EntityBundleTest.java b/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/EntityBundleTest.java
deleted file mode 100644
index 4b4df8b..0000000
--- a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/EntityBundleTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 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.migration.bundle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import static java.util.Arrays.asList;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Collections;
-
-@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
-@RunWith(JUnit4.class)
-public class EntityBundleTest {
-    @Test
-    public void schemaEquality_same_equal() {
-        EntityBundle bundle = new EntityBundle("foo", "sq",
-                asList(createFieldBundle("foo"), createFieldBundle("bar")),
-                new PrimaryKeyBundle(false, asList("foo")),
-                asList(createIndexBundle("foo")),
-                asList(createForeignKeyBundle("bar", "foo")));
-
-        EntityBundle other = new EntityBundle("foo", "sq",
-                asList(createFieldBundle("foo"), createFieldBundle("bar")),
-                new PrimaryKeyBundle(false, asList("foo")),
-                asList(createIndexBundle("foo")),
-                asList(createForeignKeyBundle("bar", "foo")));
-
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_reorderedFields_equal() {
-        EntityBundle bundle = new EntityBundle("foo", "sq",
-                asList(createFieldBundle("foo"), createFieldBundle("bar")),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        EntityBundle other = new EntityBundle("foo", "sq",
-                asList(createFieldBundle("bar"), createFieldBundle("foo")),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffFields_notEqual() {
-        EntityBundle bundle = new EntityBundle("foo", "sq",
-                asList(createFieldBundle("foo"), createFieldBundle("bar")),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        EntityBundle other = new EntityBundle("foo", "sq",
-                asList(createFieldBundle("foo2"), createFieldBundle("bar")),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_reorderedForeignKeys_equal() {
-        EntityBundle bundle = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                asList(createForeignKeyBundle("x", "y"),
-                        createForeignKeyBundle("bar", "foo")));
-
-        EntityBundle other = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                asList(createForeignKeyBundle("bar", "foo"),
-                        createForeignKeyBundle("x", "y")));
-
-
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffForeignKeys_notEqual() {
-        EntityBundle bundle = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                asList(createForeignKeyBundle("bar", "foo")));
-
-        EntityBundle other = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                Collections.<IndexBundle>emptyList(),
-                asList(createForeignKeyBundle("bar2", "foo")));
-
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_reorderedIndices_equal() {
-        EntityBundle bundle = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                asList(createIndexBundle("foo"), createIndexBundle("baz")),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        EntityBundle other = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                asList(createIndexBundle("baz"), createIndexBundle("foo")),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffIndices_notEqual() {
-        EntityBundle bundle = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                asList(createIndexBundle("foo")),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        EntityBundle other = new EntityBundle("foo", "sq",
-                Collections.<FieldBundle>emptyList(),
-                new PrimaryKeyBundle(false, asList("foo")),
-                asList(createIndexBundle("foo2")),
-                Collections.<ForeignKeyBundle>emptyList());
-
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    private FieldBundle createFieldBundle(String name) {
-        return new FieldBundle("foo", name, "text", false);
-    }
-
-    private IndexBundle createIndexBundle(String colName) {
-        return new IndexBundle("ind_" + colName, false,
-                asList(colName), "create");
-    }
-
-    private ForeignKeyBundle createForeignKeyBundle(String targetTable, String column) {
-        return new ForeignKeyBundle(targetTable, "CASCADE", "CASCADE",
-                asList(column), asList(column));
-    }
-}
diff --git a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/FieldBundleTest.java b/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/FieldBundleTest.java
deleted file mode 100644
index eac4477..0000000
--- a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/FieldBundleTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 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.migration.bundle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class FieldBundleTest {
-    @Test
-    public void schemaEquality_same_equal() {
-        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
-        FieldBundle copy = new FieldBundle("foo", "foo", "text", false);
-        assertThat(bundle.isSchemaEqual(copy), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffNonNull_notEqual() {
-        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
-        FieldBundle copy = new FieldBundle("foo", "foo", "text", true);
-        assertThat(bundle.isSchemaEqual(copy), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffColumnName_notEqual() {
-        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
-        FieldBundle copy = new FieldBundle("foo", "foo2", "text", true);
-        assertThat(bundle.isSchemaEqual(copy), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffAffinity_notEqual() {
-        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
-        FieldBundle copy = new FieldBundle("foo", "foo2", "int", false);
-        assertThat(bundle.isSchemaEqual(copy), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffPath_equal() {
-        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
-        FieldBundle copy = new FieldBundle("foo>bar", "foo", "text", false);
-        assertThat(bundle.isSchemaEqual(copy), is(true));
-    }
-}
diff --git a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundleTest.java b/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundleTest.java
deleted file mode 100644
index be1b81e..0000000
--- a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/ForeignKeyBundleTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 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.migration.bundle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-@RunWith(JUnit4.class)
-public class ForeignKeyBundleTest {
-    @Test
-    public void schemaEquality_same_equal() {
-        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffTable_notEqual() {
-        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        ForeignKeyBundle other = new ForeignKeyBundle("table2", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffOnDelete_notEqual() {
-        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete2",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffOnUpdate_notEqual() {
-        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate2", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffSrcOrder_notEqual() {
-        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col2", "col1"),
-                Arrays.asList("target1", "target2"));
-        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffTargetOrder_notEqual() {
-        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target1", "target2"));
-        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
-                "onUpdate", Arrays.asList("col1", "col2"),
-                Arrays.asList("target2", "target1"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-}
diff --git a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/IndexBundleTest.java b/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/IndexBundleTest.java
deleted file mode 100644
index aa7230f..0000000
--- a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/IndexBundleTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 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.migration.bundle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-@RunWith(JUnit4.class)
-public class IndexBundleTest {
-    @Test
-    public void schemaEquality_same_equal() {
-        IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
-        IndexBundle other = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffName_notEqual() {
-        IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
-        IndexBundle other = new IndexBundle("index3", false,
-                Arrays.asList("col1", "col2"), "sql");
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffGenericName_equal() {
-        IndexBundle bundle = new IndexBundle(IndexBundle.DEFAULT_PREFIX + "x", false,
-                Arrays.asList("col1", "col2"), "sql");
-        IndexBundle other = new IndexBundle(IndexBundle.DEFAULT_PREFIX + "y", false,
-                Arrays.asList("col1", "col2"), "sql");
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffUnique_notEqual() {
-        IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
-        IndexBundle other = new IndexBundle("index1", true,
-                Arrays.asList("col1", "col2"), "sql");
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffColumns_notEqual() {
-        IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
-        IndexBundle other = new IndexBundle("index1", false,
-                Arrays.asList("col2", "col1"), "sql");
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffSql_equal() {
-        IndexBundle bundle = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql");
-        IndexBundle other = new IndexBundle("index1", false,
-                Arrays.asList("col1", "col2"), "sql22");
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-}
diff --git a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundleTest.java b/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundleTest.java
deleted file mode 100644
index 3b9e464..0000000
--- a/room/migration/src/test/java/android/arch/persistence/room/migration/bundle/PrimaryKeyBundleTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 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.migration.bundle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-@RunWith(JUnit4.class)
-public class PrimaryKeyBundleTest {
-    @Test
-    public void schemaEquality_same_equal() {
-        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
-                Arrays.asList("foo", "bar"));
-        PrimaryKeyBundle other = new PrimaryKeyBundle(true,
-                Arrays.asList("foo", "bar"));
-        assertThat(bundle.isSchemaEqual(other), is(true));
-    }
-
-    @Test
-    public void schemaEquality_diffAutoGen_notEqual() {
-        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
-                Arrays.asList("foo", "bar"));
-        PrimaryKeyBundle other = new PrimaryKeyBundle(false,
-                Arrays.asList("foo", "bar"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffColumns_notEqual() {
-        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
-                Arrays.asList("foo", "baz"));
-        PrimaryKeyBundle other = new PrimaryKeyBundle(true,
-                Arrays.asList("foo", "bar"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-
-    @Test
-    public void schemaEquality_diffColumnOrder_notEqual() {
-        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
-                Arrays.asList("foo", "bar"));
-        PrimaryKeyBundle other = new PrimaryKeyBundle(true,
-                Arrays.asList("bar", "foo"));
-        assertThat(bundle.isSchemaEqual(other), is(false));
-    }
-}
diff --git a/room/migration/src/test/java/androidx/room/migration/bundle/EntityBundleTest.java b/room/migration/src/test/java/androidx/room/migration/bundle/EntityBundleTest.java
new file mode 100644
index 0000000..307f9d6
--- /dev/null
+++ b/room/migration/src/test/java/androidx/room/migration/bundle/EntityBundleTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 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 androidx.room.migration.bundle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@RunWith(JUnit4.class)
+public class EntityBundleTest {
+    @Test
+    public void schemaEquality_same_equal() {
+        EntityBundle bundle = new EntityBundle("foo", "sq",
+                asList(createFieldBundle("foo"), createFieldBundle("bar")),
+                new PrimaryKeyBundle(false, asList("foo")),
+                asList(createIndexBundle("foo")),
+                asList(createForeignKeyBundle("bar", "foo")));
+
+        EntityBundle other = new EntityBundle("foo", "sq",
+                asList(createFieldBundle("foo"), createFieldBundle("bar")),
+                new PrimaryKeyBundle(false, asList("foo")),
+                asList(createIndexBundle("foo")),
+                asList(createForeignKeyBundle("bar", "foo")));
+
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_reorderedFields_equal() {
+        EntityBundle bundle = new EntityBundle("foo", "sq",
+                asList(createFieldBundle("foo"), createFieldBundle("bar")),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        EntityBundle other = new EntityBundle("foo", "sq",
+                asList(createFieldBundle("bar"), createFieldBundle("foo")),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffFields_notEqual() {
+        EntityBundle bundle = new EntityBundle("foo", "sq",
+                asList(createFieldBundle("foo"), createFieldBundle("bar")),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        EntityBundle other = new EntityBundle("foo", "sq",
+                asList(createFieldBundle("foo2"), createFieldBundle("bar")),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_reorderedForeignKeys_equal() {
+        EntityBundle bundle = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                asList(createForeignKeyBundle("x", "y"),
+                        createForeignKeyBundle("bar", "foo")));
+
+        EntityBundle other = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                asList(createForeignKeyBundle("bar", "foo"),
+                        createForeignKeyBundle("x", "y")));
+
+
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffForeignKeys_notEqual() {
+        EntityBundle bundle = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                asList(createForeignKeyBundle("bar", "foo")));
+
+        EntityBundle other = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                Collections.<IndexBundle>emptyList(),
+                asList(createForeignKeyBundle("bar2", "foo")));
+
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_reorderedIndices_equal() {
+        EntityBundle bundle = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                asList(createIndexBundle("foo"), createIndexBundle("baz")),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        EntityBundle other = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                asList(createIndexBundle("baz"), createIndexBundle("foo")),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffIndices_notEqual() {
+        EntityBundle bundle = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                asList(createIndexBundle("foo")),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        EntityBundle other = new EntityBundle("foo", "sq",
+                Collections.<FieldBundle>emptyList(),
+                new PrimaryKeyBundle(false, asList("foo")),
+                asList(createIndexBundle("foo2")),
+                Collections.<ForeignKeyBundle>emptyList());
+
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    private FieldBundle createFieldBundle(String name) {
+        return new FieldBundle("foo", name, "text", false);
+    }
+
+    private IndexBundle createIndexBundle(String colName) {
+        return new IndexBundle("ind_" + colName, false,
+                asList(colName), "create");
+    }
+
+    private ForeignKeyBundle createForeignKeyBundle(String targetTable, String column) {
+        return new ForeignKeyBundle(targetTable, "CASCADE", "CASCADE",
+                asList(column), asList(column));
+    }
+}
diff --git a/room/migration/src/test/java/androidx/room/migration/bundle/FieldBundleTest.java b/room/migration/src/test/java/androidx/room/migration/bundle/FieldBundleTest.java
new file mode 100644
index 0000000..e48114a
--- /dev/null
+++ b/room/migration/src/test/java/androidx/room/migration/bundle/FieldBundleTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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 androidx.room.migration.bundle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FieldBundleTest {
+    @Test
+    public void schemaEquality_same_equal() {
+        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
+        FieldBundle copy = new FieldBundle("foo", "foo", "text", false);
+        assertThat(bundle.isSchemaEqual(copy), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffNonNull_notEqual() {
+        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
+        FieldBundle copy = new FieldBundle("foo", "foo", "text", true);
+        assertThat(bundle.isSchemaEqual(copy), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffColumnName_notEqual() {
+        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
+        FieldBundle copy = new FieldBundle("foo", "foo2", "text", true);
+        assertThat(bundle.isSchemaEqual(copy), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffAffinity_notEqual() {
+        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
+        FieldBundle copy = new FieldBundle("foo", "foo2", "int", false);
+        assertThat(bundle.isSchemaEqual(copy), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffPath_equal() {
+        FieldBundle bundle = new FieldBundle("foo", "foo", "text", false);
+        FieldBundle copy = new FieldBundle("foo>bar", "foo", "text", false);
+        assertThat(bundle.isSchemaEqual(copy), is(true));
+    }
+}
diff --git a/room/migration/src/test/java/androidx/room/migration/bundle/ForeignKeyBundleTest.java b/room/migration/src/test/java/androidx/room/migration/bundle/ForeignKeyBundleTest.java
new file mode 100644
index 0000000..198e34f
--- /dev/null
+++ b/room/migration/src/test/java/androidx/room/migration/bundle/ForeignKeyBundleTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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 androidx.room.migration.bundle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class ForeignKeyBundleTest {
+    @Test
+    public void schemaEquality_same_equal() {
+        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffTable_notEqual() {
+        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        ForeignKeyBundle other = new ForeignKeyBundle("table2", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffOnDelete_notEqual() {
+        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete2",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffOnUpdate_notEqual() {
+        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate2", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffSrcOrder_notEqual() {
+        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col2", "col1"),
+                Arrays.asList("target1", "target2"));
+        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffTargetOrder_notEqual() {
+        ForeignKeyBundle bundle = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target1", "target2"));
+        ForeignKeyBundle other = new ForeignKeyBundle("table", "onDelete",
+                "onUpdate", Arrays.asList("col1", "col2"),
+                Arrays.asList("target2", "target1"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+}
diff --git a/room/migration/src/test/java/androidx/room/migration/bundle/IndexBundleTest.java b/room/migration/src/test/java/androidx/room/migration/bundle/IndexBundleTest.java
new file mode 100644
index 0000000..82d66e2
--- /dev/null
+++ b/room/migration/src/test/java/androidx/room/migration/bundle/IndexBundleTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 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 androidx.room.migration.bundle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class IndexBundleTest {
+    @Test
+    public void schemaEquality_same_equal() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), "sql");
+        IndexBundle other = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), "sql");
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffName_notEqual() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), "sql");
+        IndexBundle other = new IndexBundle("index3", false,
+                Arrays.asList("col1", "col2"), "sql");
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffGenericName_equal() {
+        IndexBundle bundle = new IndexBundle(IndexBundle.DEFAULT_PREFIX + "x", false,
+                Arrays.asList("col1", "col2"), "sql");
+        IndexBundle other = new IndexBundle(IndexBundle.DEFAULT_PREFIX + "y", false,
+                Arrays.asList("col1", "col2"), "sql");
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffUnique_notEqual() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), "sql");
+        IndexBundle other = new IndexBundle("index1", true,
+                Arrays.asList("col1", "col2"), "sql");
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffColumns_notEqual() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), "sql");
+        IndexBundle other = new IndexBundle("index1", false,
+                Arrays.asList("col2", "col1"), "sql");
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffSql_equal() {
+        IndexBundle bundle = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), "sql");
+        IndexBundle other = new IndexBundle("index1", false,
+                Arrays.asList("col1", "col2"), "sql22");
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+}
diff --git a/room/migration/src/test/java/androidx/room/migration/bundle/PrimaryKeyBundleTest.java b/room/migration/src/test/java/androidx/room/migration/bundle/PrimaryKeyBundleTest.java
new file mode 100644
index 0000000..bae3060
--- /dev/null
+++ b/room/migration/src/test/java/androidx/room/migration/bundle/PrimaryKeyBundleTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 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 androidx.room.migration.bundle;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class PrimaryKeyBundleTest {
+    @Test
+    public void schemaEquality_same_equal() {
+        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
+                Arrays.asList("foo", "bar"));
+        PrimaryKeyBundle other = new PrimaryKeyBundle(true,
+                Arrays.asList("foo", "bar"));
+        assertThat(bundle.isSchemaEqual(other), is(true));
+    }
+
+    @Test
+    public void schemaEquality_diffAutoGen_notEqual() {
+        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
+                Arrays.asList("foo", "bar"));
+        PrimaryKeyBundle other = new PrimaryKeyBundle(false,
+                Arrays.asList("foo", "bar"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffColumns_notEqual() {
+        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
+                Arrays.asList("foo", "baz"));
+        PrimaryKeyBundle other = new PrimaryKeyBundle(true,
+                Arrays.asList("foo", "bar"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+
+    @Test
+    public void schemaEquality_diffColumnOrder_notEqual() {
+        PrimaryKeyBundle bundle = new PrimaryKeyBundle(true,
+                Arrays.asList("foo", "bar"));
+        PrimaryKeyBundle other = new PrimaryKeyBundle(true,
+                Arrays.asList("bar", "foo"));
+        assertThat(bundle.isSchemaEqual(other), is(false));
+    }
+}
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index 17948ee..cfd667d 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -1,21 +1,21 @@
-package android.arch.persistence.room {
+package androidx.room {
 
   public class DatabaseConfiguration {
     method public boolean isMigrationRequiredFrom(int);
     field public final boolean allowMainThreadQueries;
-    field public final java.util.List<android.arch.persistence.room.RoomDatabase.Callback> callbacks;
+    field public final java.util.List<androidx.room.RoomDatabase.Callback> callbacks;
     field public final android.content.Context context;
-    field public final android.arch.persistence.room.RoomDatabase.JournalMode journalMode;
-    field public final android.arch.persistence.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final androidx.room.RoomDatabase.JournalMode journalMode;
+    field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
     field public final java.lang.String name;
     field public final boolean requireMigration;
-    field public final android.arch.persistence.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
   }
 
   public class InvalidationTracker {
-    method public void addObserver(android.arch.persistence.room.InvalidationTracker.Observer);
+    method public void addObserver(androidx.room.InvalidationTracker.Observer);
     method public void refreshVersionsAsync();
-    method public void removeObserver(android.arch.persistence.room.InvalidationTracker.Observer);
+    method public void removeObserver(androidx.room.InvalidationTracker.Observer);
   }
 
   public static abstract class InvalidationTracker.Observer {
@@ -26,8 +26,8 @@
 
   public class Room {
     ctor public Room();
-    method public static <T extends android.arch.persistence.room.RoomDatabase> android.arch.persistence.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context, java.lang.Class<T>, java.lang.String);
-    method public static <T extends android.arch.persistence.room.RoomDatabase> android.arch.persistence.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context, java.lang.Class<T>);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context, java.lang.Class<T>, java.lang.String);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context, java.lang.Class<T>);
     field public static final java.lang.String MASTER_TABLE_NAME = "room_master_table";
   }
 
@@ -36,63 +36,63 @@
     method public void beginTransaction();
     method public abstract void clearAllTables();
     method public void close();
-    method public android.arch.persistence.db.SupportSQLiteStatement compileStatement(java.lang.String);
-    method protected abstract android.arch.persistence.room.InvalidationTracker createInvalidationTracker();
-    method protected abstract android.arch.persistence.db.SupportSQLiteOpenHelper createOpenHelper(android.arch.persistence.room.DatabaseConfiguration);
+    method public androidx.sqlite.db.SupportSQLiteStatement compileStatement(java.lang.String);
+    method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration);
     method public void endTransaction();
-    method public android.arch.persistence.room.InvalidationTracker getInvalidationTracker();
-    method public android.arch.persistence.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public androidx.room.InvalidationTracker getInvalidationTracker();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
     method public boolean inTransaction();
-    method public void init(android.arch.persistence.room.DatabaseConfiguration);
-    method protected void internalInitInvalidationTracker(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void init(androidx.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
     method public boolean isOpen();
     method public android.database.Cursor query(java.lang.String, java.lang.Object[]);
-    method public android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
     method public void runInTransaction(java.lang.Runnable);
     method public <V> V runInTransaction(java.util.concurrent.Callable<V>);
     method public void setTransactionSuccessful();
-    field protected java.util.List<android.arch.persistence.room.RoomDatabase.Callback> mCallbacks;
-    field protected volatile android.arch.persistence.db.SupportSQLiteDatabase mDatabase;
+    field protected java.util.List<androidx.room.RoomDatabase.Callback> mCallbacks;
+    field protected volatile androidx.sqlite.db.SupportSQLiteDatabase mDatabase;
   }
 
-  public static class RoomDatabase.Builder<T extends android.arch.persistence.room.RoomDatabase> {
-    method public android.arch.persistence.room.RoomDatabase.Builder<T> addCallback(android.arch.persistence.room.RoomDatabase.Callback);
-    method public android.arch.persistence.room.RoomDatabase.Builder<T> addMigrations(android.arch.persistence.room.migration.Migration...);
-    method public android.arch.persistence.room.RoomDatabase.Builder<T> allowMainThreadQueries();
+  public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T> addCallback(androidx.room.RoomDatabase.Callback);
+    method public androidx.room.RoomDatabase.Builder<T> addMigrations(androidx.room.migration.Migration...);
+    method public androidx.room.RoomDatabase.Builder<T> allowMainThreadQueries();
     method public T build();
-    method public android.arch.persistence.room.RoomDatabase.Builder<T> fallbackToDestructiveMigration();
-    method public android.arch.persistence.room.RoomDatabase.Builder<T> fallbackToDestructiveMigrationFrom(java.lang.Integer...);
-    method public android.arch.persistence.room.RoomDatabase.Builder<T> openHelperFactory(android.arch.persistence.db.SupportSQLiteOpenHelper.Factory);
-    method public android.arch.persistence.room.RoomDatabase.Builder<T> setJournalMode(android.arch.persistence.room.RoomDatabase.JournalMode);
+    method public androidx.room.RoomDatabase.Builder<T> fallbackToDestructiveMigration();
+    method public androidx.room.RoomDatabase.Builder<T> fallbackToDestructiveMigrationFrom(int...);
+    method public androidx.room.RoomDatabase.Builder<T> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
+    method public androidx.room.RoomDatabase.Builder<T> setJournalMode(androidx.room.RoomDatabase.JournalMode);
   }
 
   public static abstract class RoomDatabase.Callback {
     ctor public RoomDatabase.Callback();
-    method public void onCreate(android.arch.persistence.db.SupportSQLiteDatabase);
-    method public void onOpen(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
   }
 
   public static final class RoomDatabase.JournalMode extends java.lang.Enum {
-    method public static android.arch.persistence.room.RoomDatabase.JournalMode valueOf(java.lang.String);
-    method public static final android.arch.persistence.room.RoomDatabase.JournalMode[] values();
-    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode AUTOMATIC;
-    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode TRUNCATE;
-    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+    method public static androidx.room.RoomDatabase.JournalMode valueOf(java.lang.String);
+    method public static final androidx.room.RoomDatabase.JournalMode[] values();
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
   }
 
   public static class RoomDatabase.MigrationContainer {
     ctor public RoomDatabase.MigrationContainer();
-    method public void addMigrations(android.arch.persistence.room.migration.Migration...);
-    method public java.util.List<android.arch.persistence.room.migration.Migration> findMigrationPath(int, int);
+    method public void addMigrations(androidx.room.migration.Migration...);
+    method public java.util.List<androidx.room.migration.Migration> findMigrationPath(int, int);
   }
 
 }
 
-package android.arch.persistence.room.migration {
+package androidx.room.migration {
 
   public abstract class Migration {
     ctor public Migration(int, int);
-    method public abstract void migrate(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
     field public final int endVersion;
     field public final int startVersion;
   }
diff --git a/room/runtime/api/1.0.0.txt b/room/runtime/api_legacy/1.0.0.txt
similarity index 100%
rename from room/runtime/api/1.0.0.txt
rename to room/runtime/api_legacy/1.0.0.txt
diff --git a/room/runtime/api_legacy/1.1.0.txt b/room/runtime/api_legacy/1.1.0.txt
new file mode 100644
index 0000000..5a4f55b
--- /dev/null
+++ b/room/runtime/api_legacy/1.1.0.txt
@@ -0,0 +1,101 @@
+package android.arch.persistence.room {
+
+  public class DatabaseConfiguration {
+    method public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<android.arch.persistence.room.RoomDatabase.Callback> callbacks;
+    field public final android.content.Context context;
+    field public final android.arch.persistence.room.RoomDatabase.JournalMode journalMode;
+    field public final android.arch.persistence.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final java.lang.String name;
+    field public final boolean requireMigration;
+    field public final android.arch.persistence.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+  }
+
+  public class InvalidationTracker {
+    method public void addObserver(android.arch.persistence.room.InvalidationTracker.Observer);
+    method public void refreshVersionsAsync();
+    method public void removeObserver(android.arch.persistence.room.InvalidationTracker.Observer);
+  }
+
+  public static abstract class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(java.lang.String, java.lang.String...);
+    ctor public InvalidationTracker.Observer(java.lang.String[]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String>);
+  }
+
+  public class Room {
+    ctor public Room();
+    method public static <T extends android.arch.persistence.room.RoomDatabase> android.arch.persistence.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context, java.lang.Class<T>, java.lang.String);
+    method public static <T extends android.arch.persistence.room.RoomDatabase> android.arch.persistence.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context, java.lang.Class<T>);
+    field public static final java.lang.String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method public void beginTransaction();
+    method public abstract void clearAllTables();
+    method public void close();
+    method public android.arch.persistence.db.SupportSQLiteStatement compileStatement(java.lang.String);
+    method protected abstract android.arch.persistence.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract android.arch.persistence.db.SupportSQLiteOpenHelper createOpenHelper(android.arch.persistence.room.DatabaseConfiguration);
+    method public void endTransaction();
+    method public android.arch.persistence.room.InvalidationTracker getInvalidationTracker();
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public boolean inTransaction();
+    method public void init(android.arch.persistence.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(java.lang.String, java.lang.Object[]);
+    method public android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery);
+    method public void runInTransaction(java.lang.Runnable);
+    method public <V> V runInTransaction(java.util.concurrent.Callable<V>);
+    method public void setTransactionSuccessful();
+    field protected java.util.List<android.arch.persistence.room.RoomDatabase.Callback> mCallbacks;
+    field protected volatile android.arch.persistence.db.SupportSQLiteDatabase mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends android.arch.persistence.room.RoomDatabase> {
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> addCallback(android.arch.persistence.room.RoomDatabase.Callback);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> addMigrations(android.arch.persistence.room.migration.Migration...);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> allowMainThreadQueries();
+    method public T build();
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> fallbackToDestructiveMigration();
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> fallbackToDestructiveMigrationFrom(int...);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> openHelperFactory(android.arch.persistence.db.SupportSQLiteOpenHelper.Factory);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> setJournalMode(android.arch.persistence.room.RoomDatabase.JournalMode);
+  }
+
+  public static abstract class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void onOpen(android.arch.persistence.db.SupportSQLiteDatabase);
+  }
+
+  public static final class RoomDatabase.JournalMode extends java.lang.Enum {
+    method public static android.arch.persistence.room.RoomDatabase.JournalMode valueOf(java.lang.String);
+    method public static final android.arch.persistence.room.RoomDatabase.JournalMode[] values();
+    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(android.arch.persistence.room.migration.Migration...);
+    method public java.util.List<android.arch.persistence.room.migration.Migration> findMigrationPath(int, int);
+  }
+
+}
+
+package android.arch.persistence.room.migration {
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(android.arch.persistence.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
diff --git a/room/runtime/api_legacy/current.txt b/room/runtime/api_legacy/current.txt
new file mode 100644
index 0000000..5a4f55b
--- /dev/null
+++ b/room/runtime/api_legacy/current.txt
@@ -0,0 +1,101 @@
+package android.arch.persistence.room {
+
+  public class DatabaseConfiguration {
+    method public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<android.arch.persistence.room.RoomDatabase.Callback> callbacks;
+    field public final android.content.Context context;
+    field public final android.arch.persistence.room.RoomDatabase.JournalMode journalMode;
+    field public final android.arch.persistence.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final java.lang.String name;
+    field public final boolean requireMigration;
+    field public final android.arch.persistence.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+  }
+
+  public class InvalidationTracker {
+    method public void addObserver(android.arch.persistence.room.InvalidationTracker.Observer);
+    method public void refreshVersionsAsync();
+    method public void removeObserver(android.arch.persistence.room.InvalidationTracker.Observer);
+  }
+
+  public static abstract class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(java.lang.String, java.lang.String...);
+    ctor public InvalidationTracker.Observer(java.lang.String[]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String>);
+  }
+
+  public class Room {
+    ctor public Room();
+    method public static <T extends android.arch.persistence.room.RoomDatabase> android.arch.persistence.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context, java.lang.Class<T>, java.lang.String);
+    method public static <T extends android.arch.persistence.room.RoomDatabase> android.arch.persistence.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context, java.lang.Class<T>);
+    field public static final java.lang.String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method public void beginTransaction();
+    method public abstract void clearAllTables();
+    method public void close();
+    method public android.arch.persistence.db.SupportSQLiteStatement compileStatement(java.lang.String);
+    method protected abstract android.arch.persistence.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract android.arch.persistence.db.SupportSQLiteOpenHelper createOpenHelper(android.arch.persistence.room.DatabaseConfiguration);
+    method public void endTransaction();
+    method public android.arch.persistence.room.InvalidationTracker getInvalidationTracker();
+    method public android.arch.persistence.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public boolean inTransaction();
+    method public void init(android.arch.persistence.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(java.lang.String, java.lang.Object[]);
+    method public android.database.Cursor query(android.arch.persistence.db.SupportSQLiteQuery);
+    method public void runInTransaction(java.lang.Runnable);
+    method public <V> V runInTransaction(java.util.concurrent.Callable<V>);
+    method public void setTransactionSuccessful();
+    field protected java.util.List<android.arch.persistence.room.RoomDatabase.Callback> mCallbacks;
+    field protected volatile android.arch.persistence.db.SupportSQLiteDatabase mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends android.arch.persistence.room.RoomDatabase> {
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> addCallback(android.arch.persistence.room.RoomDatabase.Callback);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> addMigrations(android.arch.persistence.room.migration.Migration...);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> allowMainThreadQueries();
+    method public T build();
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> fallbackToDestructiveMigration();
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> fallbackToDestructiveMigrationFrom(int...);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> openHelperFactory(android.arch.persistence.db.SupportSQLiteOpenHelper.Factory);
+    method public android.arch.persistence.room.RoomDatabase.Builder<T> setJournalMode(android.arch.persistence.room.RoomDatabase.JournalMode);
+  }
+
+  public static abstract class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(android.arch.persistence.db.SupportSQLiteDatabase);
+    method public void onOpen(android.arch.persistence.db.SupportSQLiteDatabase);
+  }
+
+  public static final class RoomDatabase.JournalMode extends java.lang.Enum {
+    method public static android.arch.persistence.room.RoomDatabase.JournalMode valueOf(java.lang.String);
+    method public static final android.arch.persistence.room.RoomDatabase.JournalMode[] values();
+    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant public static final android.arch.persistence.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(android.arch.persistence.room.migration.Migration...);
+    method public java.util.List<android.arch.persistence.room.migration.Migration> findMigrationPath(int, int);
+  }
+
+}
+
+package android.arch.persistence.room.migration {
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(android.arch.persistence.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index 739725c..205c030 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -30,13 +30,13 @@
 }
 
 dependencies {
-    api(project(":room:common"))
-    api(project(":persistence:db-framework"))
-    api(project(":persistence:db"))
-    api(project(":arch:runtime"))
-    compileOnly project(":paging:common")
-    compileOnly project(":lifecycle:runtime")
-    compileOnly project(":lifecycle:extensions")
+    api(project(":room:room-common"))
+    api(project(":sqlite:sqlite-framework"))
+    api(project(":sqlite:sqlite"))
+    api(project(":arch:core-runtime"))
+    compileOnly project(":paging:paging-common")
+    compileOnly project(":lifecycle:lifecycle-runtime")
+    compileOnly project(":lifecycle:lifecycle-extensions")
     api(SUPPORT_CORE_UTILS, libs.support_exclude_config)
 
     testImplementation(project(":arch:core-testing"))
diff --git a/room/runtime/proguard-rules.pro b/room/runtime/proguard-rules.pro
index b293cd8..c148df3 100644
--- a/room/runtime/proguard-rules.pro
+++ b/room/runtime/proguard-rules.pro
@@ -1,2 +1,2 @@
--keep public class * extends android.arch.persistence.room.RoomDatabase
--dontwarn android.arch.persistence.room.paging.**
\ No newline at end of file
+-keep public class * extends androidx.room.RoomDatabase
+-dontwarn androidx.room.paging.**
\ No newline at end of file
diff --git a/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java b/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java
deleted file mode 100644
index 0eb35f6..0000000
--- a/room/runtime/src/androidTest/java/android/arch/persistence/room/migration/TableInfoTest.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration;
-
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
-import android.arch.persistence.room.util.TableInfo;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Pair;
-
-import org.junit.After;
-import org.junit.Test;
-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;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class TableInfoTest {
-    private SupportSQLiteDatabase mDb;
-
-    @Test
-    public void readSimple() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
-                        + "name TEXT)");
-        TableInfo info = TableInfo.read(mDb, "foo");
-        assertThat(info, is(new TableInfo("foo",
-                toMap(new TableInfo.Column("id", "INTEGER", false, 1),
-                        new TableInfo.Column("name", "TEXT", false, 0)),
-                Collections.<TableInfo.ForeignKey>emptySet())));
-    }
-
-    @Test
-    public void multiplePrimaryKeys() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (id INTEGER,"
-                        + "name TEXT, PRIMARY KEY(name, id))");
-        TableInfo info = TableInfo.read(mDb, "foo");
-        assertThat(info, is(new TableInfo("foo",
-                toMap(new TableInfo.Column("id", "INTEGER", false, 2),
-                        new TableInfo.Column("name", "TEXT", false, 1)),
-                Collections.<TableInfo.ForeignKey>emptySet())));
-    }
-
-    @Test
-    public void alteredTable() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (id INTEGER,"
-                        + "name TEXT, PRIMARY KEY(name))");
-        mDb.execSQL("ALTER TABLE foo ADD COLUMN added REAL;");
-        TableInfo info = TableInfo.read(mDb, "foo");
-        assertThat(info, is(new TableInfo("foo",
-                toMap(new TableInfo.Column("id", "INTEGER", false, 0),
-                        new TableInfo.Column("name", "TEXT", false, 1),
-                        new TableInfo.Column("added", "REAL", false, 0)),
-                Collections.<TableInfo.ForeignKey>emptySet())));
-    }
-
-    @Test
-    public void nonNull() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (name TEXT NOT NULL)");
-        TableInfo info = TableInfo.read(mDb, "foo");
-        assertThat(info, is(new TableInfo("foo",
-                toMap(new TableInfo.Column("name", "TEXT", true, 0)),
-                Collections.<TableInfo.ForeignKey>emptySet())));
-    }
-
-    @Test
-    public void defaultValue() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (name TEXT DEFAULT blah)");
-        TableInfo info = TableInfo.read(mDb, "foo");
-        assertThat(info, is(new TableInfo(
-                "foo",
-                toMap(new TableInfo.Column("name", "TEXT", false, 0)),
-                Collections.<TableInfo.ForeignKey>emptySet())));
-    }
-
-    @Test
-    public void foreignKey() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (name TEXT)",
-                "CREATE TABLE bar(barName TEXT, FOREIGN KEY(barName) REFERENCES foo(name))"
-        );
-        TableInfo info = TableInfo.read(mDb, "bar");
-        assertThat(info.foreignKeys.size(), is(1));
-        final TableInfo.ForeignKey foreignKey = info.foreignKeys.iterator().next();
-        assertThat(foreignKey.columnNames, is(singletonList("barName")));
-        assertThat(foreignKey.referenceColumnNames, is(singletonList("name")));
-        assertThat(foreignKey.onDelete, is("NO ACTION"));
-        assertThat(foreignKey.onUpdate, is("NO ACTION"));
-        assertThat(foreignKey.referenceTable, is("foo"));
-    }
-
-    @Test
-    public void multipleForeignKeys() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (name TEXT, lastName TEXT)",
-                "CREATE TABLE foo2 (name TEXT, lastName TEXT)",
-                "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
-                        + " FOREIGN KEY(barName) REFERENCES foo(name) ON UPDATE SET NULL,"
-                        + " FOREIGN KEY(barLastName) REFERENCES foo2(lastName) ON DELETE CASCADE)");
-        TableInfo info = TableInfo.read(mDb, "bar");
-        assertThat(info.foreignKeys.size(), is(2));
-        Set<TableInfo.ForeignKey> expected = new HashSet<>();
-        expected.add(new TableInfo.ForeignKey("foo2", // table
-                "CASCADE", // on delete
-                "NO ACTION", // on update
-                singletonList("barLastName"), // my
-                singletonList("lastName")) // ref
-        );
-        expected.add(new TableInfo.ForeignKey("foo", // table
-                "NO ACTION", // on delete
-                "SET NULL", // on update
-                singletonList("barName"), // mine
-                singletonList("name")/*ref*/));
-        assertThat(info.foreignKeys, equalTo(expected));
-    }
-
-    @Test
-    public void compositeForeignKey() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (name TEXT, lastName TEXT)",
-                "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
-                        + " FOREIGN KEY(barName, barLastName) REFERENCES foo(name, lastName)"
-                        + " ON UPDATE cascade ON DELETE RESTRICT)");
-        TableInfo info = TableInfo.read(mDb, "bar");
-        assertThat(info.foreignKeys.size(), is(1));
-        TableInfo.ForeignKey expected = new TableInfo.ForeignKey(
-                "foo", // table
-                "RESTRICT", // on delete
-                "CASCADE", // on update
-                asList("barName", "barLastName"), // my columns
-                asList("name", "lastName") // ref columns
-        );
-        assertThat(info.foreignKeys.iterator().next(), is(expected));
-    }
-
-    @Test
-    public void caseInsensitiveTypeName() {
-        mDb = createDatabase(
-                "CREATE TABLE foo (n integer)");
-        TableInfo info = TableInfo.read(mDb, "foo");
-        assertThat(info, is(new TableInfo(
-                "foo",
-                toMap(new TableInfo.Column("n", "INTEGER", false, 0)),
-                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"))))
-        ));
-    }
-
-    @Test
-    public void compatColumnTypes() {
-        // see:https://www.sqlite.org/datatype3.html 3.1
-        List<Pair<String, String>> testCases = Arrays.asList(
-                new Pair<>("TINYINT", "integer"),
-                new Pair<>("VARCHAR", "text"),
-                new Pair<>("DOUBLE", "real"),
-                new Pair<>("BOOLEAN", "numeric"),
-                new Pair<>("FLOATING POINT", "integer")
-        );
-        for (Pair<String, String> testCase : testCases) {
-            mDb = createDatabase(
-                    "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
-                            + "name " + testCase.first + ")");
-            TableInfo info = TableInfo.read(mDb, "foo");
-            assertThat(info, is(new TableInfo("foo",
-                    toMap(new TableInfo.Column("id", "INTEGER", false, 1),
-                            new TableInfo.Column("name", testCase.second, false, 0)),
-                    Collections.<TableInfo.ForeignKey>emptySet())));
-        }
-    }
-
-    private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
-        Map<String, TableInfo.Column> result = new HashMap<>();
-        for (TableInfo.Column column : columns) {
-            result.put(column.name, column);
-        }
-        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()) {
-            mDb.close();
-        }
-    }
-
-    private static SupportSQLiteDatabase createDatabase(final String... queries) {
-        return new FrameworkSQLiteOpenHelperFactory().create(
-                SupportSQLiteOpenHelper.Configuration
-                        .builder(InstrumentationRegistry.getTargetContext())
-                        .name(null)
-                        .callback(new SupportSQLiteOpenHelper.Callback(1) {
-                            @Override
-                            public void onCreate(SupportSQLiteDatabase db) {
-                                for (String query : queries) {
-                                    db.execSQL(query);
-                                }
-                            }
-
-                            @Override
-                            public void onUpgrade(SupportSQLiteDatabase db, int oldVersion,
-                                    int newVersion) {
-                                throw new IllegalStateException("should not be upgrading");
-                            }
-                        }).build()
-        ).getWritableDatabase();
-    }
-}
diff --git a/room/runtime/src/androidTest/java/androidx/room/migration/TableInfoTest.java b/room/runtime/src/androidTest/java/androidx/room/migration/TableInfoTest.java
new file mode 100644
index 0000000..8b145db
--- /dev/null
+++ b/room/runtime/src/androidTest/java/androidx/room/migration/TableInfoTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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 androidx.room.migration;
+
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
+
+import androidx.room.util.TableInfo;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+
+import org.junit.After;
+import org.junit.Test;
+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;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TableInfoTest {
+    private SupportSQLiteDatabase mDb;
+
+    @Test
+    public void readSimple() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
+                        + "name TEXT)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("id", "INTEGER", false, 1),
+                        new TableInfo.Column("name", "TEXT", false, 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void multiplePrimaryKeys() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (id INTEGER,"
+                        + "name TEXT, PRIMARY KEY(name, id))");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("id", "INTEGER", false, 2),
+                        new TableInfo.Column("name", "TEXT", false, 1)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void alteredTable() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (id INTEGER,"
+                        + "name TEXT, PRIMARY KEY(name))");
+        mDb.execSQL("ALTER TABLE foo ADD COLUMN added REAL;");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("id", "INTEGER", false, 0),
+                        new TableInfo.Column("name", "TEXT", false, 1),
+                        new TableInfo.Column("added", "REAL", false, 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void nonNull() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT NOT NULL)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo("foo",
+                toMap(new TableInfo.Column("name", "TEXT", true, 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void defaultValue() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT DEFAULT blah)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo(
+                "foo",
+                toMap(new TableInfo.Column("name", "TEXT", false, 0)),
+                Collections.<TableInfo.ForeignKey>emptySet())));
+    }
+
+    @Test
+    public void foreignKey() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT)",
+                "CREATE TABLE bar(barName TEXT, FOREIGN KEY(barName) REFERENCES foo(name))"
+        );
+        TableInfo info = TableInfo.read(mDb, "bar");
+        assertThat(info.foreignKeys.size(), is(1));
+        final TableInfo.ForeignKey foreignKey = info.foreignKeys.iterator().next();
+        assertThat(foreignKey.columnNames, is(singletonList("barName")));
+        assertThat(foreignKey.referenceColumnNames, is(singletonList("name")));
+        assertThat(foreignKey.onDelete, is("NO ACTION"));
+        assertThat(foreignKey.onUpdate, is("NO ACTION"));
+        assertThat(foreignKey.referenceTable, is("foo"));
+    }
+
+    @Test
+    public void multipleForeignKeys() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT, lastName TEXT)",
+                "CREATE TABLE foo2 (name TEXT, lastName TEXT)",
+                "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
+                        + " FOREIGN KEY(barName) REFERENCES foo(name) ON UPDATE SET NULL,"
+                        + " FOREIGN KEY(barLastName) REFERENCES foo2(lastName) ON DELETE CASCADE)");
+        TableInfo info = TableInfo.read(mDb, "bar");
+        assertThat(info.foreignKeys.size(), is(2));
+        Set<TableInfo.ForeignKey> expected = new HashSet<>();
+        expected.add(new TableInfo.ForeignKey("foo2", // table
+                "CASCADE", // on delete
+                "NO ACTION", // on update
+                singletonList("barLastName"), // my
+                singletonList("lastName")) // ref
+        );
+        expected.add(new TableInfo.ForeignKey("foo", // table
+                "NO ACTION", // on delete
+                "SET NULL", // on update
+                singletonList("barName"), // mine
+                singletonList("name")/*ref*/));
+        assertThat(info.foreignKeys, equalTo(expected));
+    }
+
+    @Test
+    public void compositeForeignKey() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (name TEXT, lastName TEXT)",
+                "CREATE TABLE bar(barName TEXT, barLastName TEXT, "
+                        + " FOREIGN KEY(barName, barLastName) REFERENCES foo(name, lastName)"
+                        + " ON UPDATE cascade ON DELETE RESTRICT)");
+        TableInfo info = TableInfo.read(mDb, "bar");
+        assertThat(info.foreignKeys.size(), is(1));
+        TableInfo.ForeignKey expected = new TableInfo.ForeignKey(
+                "foo", // table
+                "RESTRICT", // on delete
+                "CASCADE", // on update
+                asList("barName", "barLastName"), // my columns
+                asList("name", "lastName") // ref columns
+        );
+        assertThat(info.foreignKeys.iterator().next(), is(expected));
+    }
+
+    @Test
+    public void caseInsensitiveTypeName() {
+        mDb = createDatabase(
+                "CREATE TABLE foo (n integer)");
+        TableInfo info = TableInfo.read(mDb, "foo");
+        assertThat(info, is(new TableInfo(
+                "foo",
+                toMap(new TableInfo.Column("n", "INTEGER", false, 0)),
+                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"))))
+        ));
+    }
+
+    @Test
+    public void compatColumnTypes() {
+        // see:https://www.sqlite.org/datatype3.html 3.1
+        List<Pair<String, String>> testCases = Arrays.asList(
+                new Pair<>("TINYINT", "integer"),
+                new Pair<>("VARCHAR", "text"),
+                new Pair<>("DOUBLE", "real"),
+                new Pair<>("BOOLEAN", "numeric"),
+                new Pair<>("FLOATING POINT", "integer")
+        );
+        for (Pair<String, String> testCase : testCases) {
+            mDb = createDatabase(
+                    "CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT,"
+                            + "name " + testCase.first + ")");
+            TableInfo info = TableInfo.read(mDb, "foo");
+            assertThat(info, is(new TableInfo("foo",
+                    toMap(new TableInfo.Column("id", "INTEGER", false, 1),
+                            new TableInfo.Column("name", testCase.second, false, 0)),
+                    Collections.<TableInfo.ForeignKey>emptySet())));
+        }
+    }
+
+    private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
+        Map<String, TableInfo.Column> result = new HashMap<>();
+        for (TableInfo.Column column : columns) {
+            result.put(column.name, column);
+        }
+        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()) {
+            mDb.close();
+        }
+    }
+
+    private static SupportSQLiteDatabase createDatabase(final String... queries) {
+        return new FrameworkSQLiteOpenHelperFactory().create(
+                SupportSQLiteOpenHelper.Configuration
+                        .builder(InstrumentationRegistry.getTargetContext())
+                        .name(null)
+                        .callback(new SupportSQLiteOpenHelper.Callback(1) {
+                            @Override
+                            public void onCreate(SupportSQLiteDatabase db) {
+                                for (String query : queries) {
+                                    db.execSQL(query);
+                                }
+                            }
+
+                            @Override
+                            public void onUpgrade(SupportSQLiteDatabase db, int oldVersion,
+                                    int newVersion) {
+                                throw new IllegalStateException("should not be upgrading");
+                            }
+                        }).build()
+        ).getWritableDatabase();
+    }
+}
diff --git a/room/runtime/src/main/AndroidManifest.xml b/room/runtime/src/main/AndroidManifest.xml
index 54a5b36..80e6de8 100644
--- a/room/runtime/src/main/AndroidManifest.xml
+++ b/room/runtime/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.persistence.room">
+          package="androidx.room">
 </manifest>
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java b/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java
deleted file mode 100644
index d032bff..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/DatabaseConfiguration.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Configuration class for a {@link RoomDatabase}.
- */
-@SuppressWarnings("WeakerAccess")
-public class DatabaseConfiguration {
-    /**
-     * The factory to use to access the database.
-     */
-    @NonNull
-    public final SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
-    /**
-     * The context to use while connecting to the database.
-     */
-    @NonNull
-    public final Context context;
-    /**
-     * The name of the database file or null if it is an in-memory database.
-     */
-    @Nullable
-    public final String name;
-
-    /**
-     * Collection of available migrations.
-     */
-    @NonNull
-    public final RoomDatabase.MigrationContainer migrationContainer;
-
-    @Nullable
-    public final List<RoomDatabase.Callback> callbacks;
-
-    /**
-     * Whether Room should throw an exception for queries run on the main thread.
-     */
-    public final boolean allowMainThreadQueries;
-
-    /**
-     * The journal mode for this database.
-     */
-    public final RoomDatabase.JournalMode journalMode;
-
-    /**
-     * If true, Room should crash if a migration is missing.
-     */
-    public final boolean requireMigration;
-
-    /**
-     * The collection of schema versions from which migrations aren't required.
-     */
-    private final Set<Integer> mMigrationNotRequiredFrom;
-
-    /**
-     * Creates a database configuration with the given values.
-     *
-     * @param context The application context.
-     * @param name Name of the database, can be null if it is in memory.
-     * @param sqliteOpenHelperFactory The open helper factory to use.
-     * @param migrationContainer The migration container for migrations.
-     * @param callbacks The list of callbacks for database events.
-     * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
-     * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
-     * @param requireMigration True if Room should require a valid migration if version changes,
-     *                        instead of recreating the tables.
-     * @param migrationNotRequiredFrom The collection of schema versions from which migrations
-     *                                 aren't required.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
-            @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
-            @NonNull RoomDatabase.MigrationContainer migrationContainer,
-            @Nullable List<RoomDatabase.Callback> callbacks,
-            boolean allowMainThreadQueries,
-            RoomDatabase.JournalMode journalMode,
-            boolean requireMigration,
-            @Nullable Set<Integer> migrationNotRequiredFrom) {
-        this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
-        this.context = context;
-        this.name = name;
-        this.migrationContainer = migrationContainer;
-        this.callbacks = callbacks;
-        this.allowMainThreadQueries = allowMainThreadQueries;
-        this.journalMode = journalMode;
-        this.requireMigration = requireMigration;
-        this.mMigrationNotRequiredFrom = migrationNotRequiredFrom;
-    }
-
-    /**
-     * Returns whether a migration is required from the specified version.
-     *
-     * @param version  The schema version.
-     * @return True if a valid migration is required, false otherwise.
-     */
-    public boolean isMigrationRequiredFrom(int version) {
-        // Migrations are required from this version if we generally require migrations AND EITHER
-        // there are no exceptions OR the supplied version is not one of the exceptions.
-        return requireMigration
-                && (mMigrationNotRequiredFrom == null
-                || !mMigrationNotRequiredFrom.contains(version));
-
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java b/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
deleted file mode 100644
index 373b122..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.support.annotation.RestrictTo;
-
-/**
- * Implementations of this class knows how to delete or update a particular entity.
- * <p>
- * This is an internal library class and all of its implementations are auto-generated.
- *
- * @param <T> The type parameter of the entity to be deleted
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@SuppressWarnings({"WeakerAccess", "unused"})
-public abstract class EntityDeletionOrUpdateAdapter<T> extends SharedSQLiteStatement {
-    /**
-     * Creates a DeletionOrUpdateAdapter that can delete or update the entity type T on the given
-     * database.
-     *
-     * @param database The database to delete / update the item in.
-     */
-    public EntityDeletionOrUpdateAdapter(RoomDatabase database) {
-        super(database);
-    }
-
-    /**
-     * Create the deletion or update query
-     *
-     * @return An SQL query that can delete or update instances of T.
-     */
-    @Override
-    protected abstract String createQuery();
-
-    /**
-     * Binds the entity into the given statement.
-     *
-     * @param statement The SQLite statement that prepared for the query returned from
-     *                  createQuery.
-     * @param entity    The entity of type T.
-     */
-    protected abstract void bind(SupportSQLiteStatement statement, T entity);
-
-    /**
-     * Deletes or updates the given entities in the database and returns the affected row count.
-     *
-     * @param entity The entity to delete or update
-     * @return The number of affected rows
-     */
-    public final int handle(T entity) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            bind(stmt, entity);
-            return stmt.executeUpdateDelete();
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Deletes or updates the given entities in the database and returns the affected row count.
-     *
-     * @param entities Entities to delete or update
-     * @return The number of affected rows
-     */
-    public final int handleMultiple(Iterable<T> entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            int total = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                total += stmt.executeUpdateDelete();
-            }
-            return total;
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Deletes or updates the given entities in the database and returns the affected row count.
-     *
-     * @param entities Entities to delete or update
-     * @return The number of affected rows
-     */
-    public final int handleMultiple(T[] entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            int total = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                total += stmt.executeUpdateDelete();
-            }
-            return total;
-        } finally {
-            release(stmt);
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/EntityInsertionAdapter.java b/room/runtime/src/main/java/android/arch/persistence/room/EntityInsertionAdapter.java
deleted file mode 100644
index 6cfa332..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/EntityInsertionAdapter.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.support.annotation.RestrictTo;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Implementations of this class knows how to insert a particular entity.
- * <p>
- * This is an internal library class and all of its implementations are auto-generated.
- *
- * @param <T> The type parameter of the entity to be inserted
- * @hide
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class EntityInsertionAdapter<T> extends SharedSQLiteStatement {
-    /**
-     * Creates an InsertionAdapter that can insert the entity type T into the given database.
-     *
-     * @param database The database to insert into.
-     */
-    public EntityInsertionAdapter(RoomDatabase database) {
-        super(database);
-    }
-
-    /**
-     * Binds the entity into the given statement.
-     *
-     * @param statement The SQLite statement that prepared for the query returned from
-     *                  createInsertQuery.
-     * @param entity    The entity of type T.
-     */
-    protected abstract void bind(SupportSQLiteStatement statement, T entity);
-
-    /**
-     * Inserts the entity into the database.
-     *
-     * @param entity The entity to insert
-     */
-    public final void insert(T entity) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            bind(stmt, entity);
-            stmt.executeInsert();
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database.
-     *
-     * @param entities Entities to insert
-     */
-    public final void insert(T[] entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            for (T entity : entities) {
-                bind(stmt, entity);
-                stmt.executeInsert();
-            }
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database.
-     *
-     * @param entities Entities to insert
-     */
-    public final void insert(Iterable<T> entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            for (T entity : entities) {
-                bind(stmt, entity);
-                stmt.executeInsert();
-            }
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entity into the database and returns the row id.
-     *
-     * @param entity The entity to insert
-     * @return The SQLite row id
-     */
-    public final long insertAndReturnId(T entity) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            bind(stmt, entity);
-            return stmt.executeInsert();
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database and returns the row ids.
-     *
-     * @param entities Entities to insert
-     * @return The SQLite row ids
-     */
-    public final long[] insertAndReturnIdsArray(Collection<T> entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            final long[] result = new long[entities.size()];
-            int index = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                result[index] = stmt.executeInsert();
-                index++;
-            }
-            return result;
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database and returns the row ids.
-     *
-     * @param entities Entities to insert
-     * @return The SQLite row ids
-     */
-    public final long[] insertAndReturnIdsArray(T[] entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            final long[] result = new long[entities.length];
-            int index = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                result[index] = stmt.executeInsert();
-                index++;
-            }
-            return result;
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database and returns the row ids.
-     *
-     * @param entities Entities to insert
-     * @return The SQLite row ids
-     */
-    public final Long[] insertAndReturnIdsArrayBox(Collection<T> entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            final Long[] result = new Long[entities.size()];
-            int index = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                result[index] = stmt.executeInsert();
-                index++;
-            }
-            return result;
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database and returns the row ids.
-     *
-     * @param entities Entities to insert
-     * @return The SQLite row ids
-     */
-    public final Long[] insertAndReturnIdsArrayBox(T[] entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            final Long[] result = new Long[entities.length];
-            int index = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                result[index] = stmt.executeInsert();
-                index++;
-            }
-            return result;
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database and returns the row ids.
-     *
-     * @param entities Entities to insert
-     * @return The SQLite row ids
-     */
-    public final List<Long> insertAndReturnIdsList(T[] entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            final List<Long> result = new ArrayList<>(entities.length);
-            int index = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                result.add(index, stmt.executeInsert());
-                index++;
-            }
-            return result;
-        } finally {
-            release(stmt);
-        }
-    }
-
-    /**
-     * Inserts the given entities into the database and returns the row ids.
-     *
-     * @param entities Entities to insert
-     * @return The SQLite row ids
-     */
-    public final List<Long> insertAndReturnIdsList(Collection<T> entities) {
-        final SupportSQLiteStatement stmt = acquire();
-        try {
-            final List<Long> result = new ArrayList<>(entities.size());
-            int index = 0;
-            for (T entity : entities) {
-                bind(stmt, entity);
-                result.add(index, stmt.executeInsert());
-                index++;
-            }
-            return result;
-        } finally {
-            release(stmt);
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java b/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java
deleted file mode 100644
index 7bb4fb2..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/InvalidationTracker.java
+++ /dev/null
@@ -1,691 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import android.support.v4.util.ArrayMap;
-import android.support.v4.util.ArraySet;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Lock;
-
-/**
- * InvalidationTracker keeps a list of tables modified by queries and notifies its callbacks about
- * these tables.
- */
-// We create an in memory table with (version, table_id) where version is an auto-increment primary
-// key and a table_id (hardcoded int from initialization).
-// ObservedTableTracker tracks list of tables we should be watching (e.g. adding triggers for).
-// Before each beginTransaction, RoomDatabase invokes InvalidationTracker to sync trigger states.
-// After each endTransaction, RoomDatabase invokes InvalidationTracker to refresh invalidated
-// tables.
-// Each update on one of the observed tables triggers an insertion into this table, hence a
-// new version.
-// Unfortunately, we cannot override the previous row because sqlite uses the conflict resolution
-// of the outer query (the thing that triggered us) so we do a cleanup as we sync instead of letting
-// SQLite override the rows.
-// https://sqlite.org/lang_createtrigger.html:  An ON CONFLICT clause may be specified as part of an
-// UPDATE or INSERT action within the body of the trigger. However if an ON CONFLICT clause is
-// specified as part of the statement causing the trigger to fire, then conflict handling policy of
-// the outer statement is used instead.
-public class InvalidationTracker {
-
-    private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"};
-
-    private static final String UPDATE_TABLE_NAME = "room_table_modification_log";
-
-    private static final String VERSION_COLUMN_NAME = "version";
-
-    private static final String TABLE_ID_COLUMN_NAME = "table_id";
-
-    private static final String CREATE_VERSION_TABLE_SQL = "CREATE TEMP TABLE " + UPDATE_TABLE_NAME
-            + "(" + VERSION_COLUMN_NAME
-            + " INTEGER PRIMARY KEY AUTOINCREMENT, "
-            + TABLE_ID_COLUMN_NAME
-            + " INTEGER)";
-
-    @VisibleForTesting
-    static final String CLEANUP_SQL = "DELETE FROM " + UPDATE_TABLE_NAME
-            + " WHERE " + VERSION_COLUMN_NAME + " NOT IN( SELECT MAX("
-            + VERSION_COLUMN_NAME + ") FROM " + UPDATE_TABLE_NAME
-            + " GROUP BY " + TABLE_ID_COLUMN_NAME + ")";
-
-    @VisibleForTesting
-    // We always clean before selecting so it is unlikely to have the same row twice and if we
-    // do, it is not a big deal, just more data in the cursor.
-    static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME
-            + " WHERE " + VERSION_COLUMN_NAME
-            + "  > ? ORDER BY " + VERSION_COLUMN_NAME + " ASC;";
-
-    @NonNull
-    @VisibleForTesting
-    ArrayMap<String, Integer> mTableIdLookup;
-    private String[] mTableNames;
-
-    @NonNull
-    @VisibleForTesting
-    long[] mTableVersions;
-
-    private Object[] mQueryArgs = new Object[1];
-
-    // max id in the last syc
-    private long mMaxVersion = 0;
-
-    private final RoomDatabase mDatabase;
-
-    AtomicBoolean mPendingRefresh = new AtomicBoolean(false);
-
-    private volatile boolean mInitialized = false;
-
-    private volatile SupportSQLiteStatement mCleanupStatement;
-
-    private ObservedTableTracker mObservedTableTracker;
-
-    // should be accessed with synchronization only.
-    @VisibleForTesting
-    final SafeIterableMap<Observer, ObserverWrapper> mObserverMap = new SafeIterableMap<>();
-
-    /**
-     * Used by the generated code.
-     *
-     * @hide
-     */
-    @SuppressWarnings("WeakerAccess")
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public InvalidationTracker(RoomDatabase database, String... tableNames) {
-        mDatabase = database;
-        mObservedTableTracker = new ObservedTableTracker(tableNames.length);
-        mTableIdLookup = new ArrayMap<>();
-        final int size = tableNames.length;
-        mTableNames = new String[size];
-        for (int id = 0; id < size; id++) {
-            final String tableName = tableNames[id].toLowerCase(Locale.US);
-            mTableIdLookup.put(tableName, id);
-            mTableNames[id] = tableName;
-        }
-        mTableVersions = new long[tableNames.length];
-        Arrays.fill(mTableVersions, 0);
-    }
-
-    /**
-     * Internal method to initialize table tracking.
-     * <p>
-     * You should never call this method, it is called by the generated code.
-     */
-    void internalInit(SupportSQLiteDatabase database) {
-        synchronized (this) {
-            if (mInitialized) {
-                Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/.");
-                return;
-            }
-
-            database.beginTransaction();
-            try {
-                database.execSQL("PRAGMA temp_store = MEMORY;");
-                database.execSQL("PRAGMA recursive_triggers='ON';");
-                database.execSQL(CREATE_VERSION_TABLE_SQL);
-                database.setTransactionSuccessful();
-            } finally {
-                database.endTransaction();
-            }
-            syncTriggers(database);
-            mCleanupStatement = database.compileStatement(CLEANUP_SQL);
-            mInitialized = true;
-        }
-    }
-
-    private static void appendTriggerName(StringBuilder builder, String tableName,
-            String triggerType) {
-        builder.append("`")
-                .append("room_table_modification_trigger_")
-                .append(tableName)
-                .append("_")
-                .append(triggerType)
-                .append("`");
-    }
-
-    private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
-        final String tableName = mTableNames[tableId];
-        StringBuilder stringBuilder = new StringBuilder();
-        for (String trigger : TRIGGERS) {
-            stringBuilder.setLength(0);
-            stringBuilder.append("DROP TRIGGER IF EXISTS ");
-            appendTriggerName(stringBuilder, tableName, trigger);
-            writableDb.execSQL(stringBuilder.toString());
-        }
-    }
-
-    private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
-        final String tableName = mTableNames[tableId];
-        StringBuilder stringBuilder = new StringBuilder();
-        for (String trigger : TRIGGERS) {
-            stringBuilder.setLength(0);
-            stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS ");
-            appendTriggerName(stringBuilder, tableName, trigger);
-            stringBuilder.append(" AFTER ")
-                    .append(trigger)
-                    .append(" ON `")
-                    .append(tableName)
-                    .append("` BEGIN INSERT OR REPLACE INTO ")
-                    .append(UPDATE_TABLE_NAME)
-                    .append(" VALUES(null, ")
-                    .append(tableId)
-                    .append("); END");
-            writableDb.execSQL(stringBuilder.toString());
-        }
-    }
-
-    /**
-     * Adds the given observer to the observers list and it will be notified if any table it
-     * observes changes.
-     * <p>
-     * Database changes are pulled on another thread so in some race conditions, the observer might
-     * be invoked for changes that were done before it is added.
-     * <p>
-     * If the observer already exists, this is a no-op call.
-     * <p>
-     * If one of the tables in the Observer does not exist in the database, this method throws an
-     * {@link IllegalArgumentException}.
-     *
-     * @param observer The observer which listens the database for changes.
-     */
-    @WorkerThread
-    public void addObserver(@NonNull Observer observer) {
-        final String[] tableNames = observer.mTables;
-        int[] tableIds = new int[tableNames.length];
-        final int size = tableNames.length;
-        long[] versions = new long[tableNames.length];
-
-        // TODO sync versions ?
-        for (int i = 0; i < size; i++) {
-            Integer tableId = mTableIdLookup.get(tableNames[i].toLowerCase(Locale.US));
-            if (tableId == null) {
-                throw new IllegalArgumentException("There is no table with name " + tableNames[i]);
-            }
-            tableIds[i] = tableId;
-            versions[i] = mMaxVersion;
-        }
-        ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames, versions);
-        ObserverWrapper currentObserver;
-        synchronized (mObserverMap) {
-            currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
-        }
-        if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
-            syncTriggers();
-        }
-    }
-
-    /**
-     * Adds an observer but keeps a weak reference back to it.
-     * <p>
-     * Note that you cannot remove this observer once added. It will be automatically removed
-     * when the observer is GC'ed.
-     *
-     * @param observer The observer to which InvalidationTracker will keep a weak reference.
-     * @hide
-     */
-    @SuppressWarnings("unused")
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public void addWeakObserver(Observer observer) {
-        addObserver(new WeakObserver(this, observer));
-    }
-
-    /**
-     * Removes the observer from the observers list.
-     *
-     * @param observer The observer to remove.
-     */
-    @SuppressWarnings("WeakerAccess")
-    @WorkerThread
-    public void removeObserver(@NonNull final Observer observer) {
-        ObserverWrapper wrapper;
-        synchronized (mObserverMap) {
-            wrapper = mObserverMap.remove(observer);
-        }
-        if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
-            syncTriggers();
-        }
-    }
-
-    private boolean ensureInitialization() {
-        if (!mDatabase.isOpen()) {
-            return false;
-        }
-        if (!mInitialized) {
-            // trigger initialization
-            mDatabase.getOpenHelper().getWritableDatabase();
-        }
-        if (!mInitialized) {
-            Log.e(Room.LOG_TAG, "database is not initialized even though it is open");
-            return false;
-        }
-        return true;
-    }
-
-    @VisibleForTesting
-    Runnable mRefreshRunnable = new Runnable() {
-        @Override
-        public void run() {
-            final Lock closeLock = mDatabase.getCloseLock();
-            boolean hasUpdatedTable = false;
-            try {
-                closeLock.lock();
-
-                if (!ensureInitialization()) {
-                    return;
-                }
-
-                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;
-                if (mDatabase.mWriteAheadLoggingEnabled) {
-                    // This transaction has to be on the underlying DB rather than the RoomDatabase
-                    // in order to avoid a recursive loop after endTransaction.
-                    SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
-                    try {
-                        db.beginTransaction();
-                        hasUpdatedTable = checkUpdatedTable();
-                        db.setTransactionSuccessful();
-                    } finally {
-                        db.endTransaction();
-                    }
-                } else {
-                    hasUpdatedTable = checkUpdatedTable();
-                }
-            } catch (IllegalStateException | SQLiteException exception) {
-                // may happen if db is closed. just log.
-                Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
-                        exception);
-            } finally {
-                closeLock.unlock();
-            }
-            if (hasUpdatedTable) {
-                synchronized (mObserverMap) {
-                    for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
-                        entry.getValue().checkForInvalidation(mTableVersions);
-                    }
-                }
-            }
-        }
-
-        private boolean checkUpdatedTable() {
-            boolean hasUpdatedTable = false;
-            Cursor cursor = mDatabase.query(SELECT_UPDATED_TABLES_SQL, mQueryArgs);
-            //noinspection TryFinallyCanBeTryWithResources
-            try {
-                while (cursor.moveToNext()) {
-                    final long version = cursor.getLong(0);
-                    final int tableId = cursor.getInt(1);
-
-                    mTableVersions[tableId] = version;
-                    hasUpdatedTable = true;
-                    // result is ordered so we can safely do this assignment
-                    mMaxVersion = version;
-                }
-            } finally {
-                cursor.close();
-            }
-            return hasUpdatedTable;
-        }
-    };
-
-    /**
-     * Enqueues a task to refresh the list of updated tables.
-     * <p>
-     * This method is automatically called when {@link RoomDatabase#endTransaction()} is called but
-     * if you have another connection to the database or directly use {@link
-     * SupportSQLiteDatabase}, you may need to call this manually.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public void refreshVersionsAsync() {
-        // TODO we should consider doing this sync instead of async.
-        if (mPendingRefresh.compareAndSet(false, true)) {
-            ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
-        }
-    }
-
-    /**
-     * Check versions for tables, and run observers synchronously if tables have been updated.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @WorkerThread
-    public void refreshVersionsSync() {
-        syncTriggers();
-        mRefreshRunnable.run();
-    }
-
-    void syncTriggers(SupportSQLiteDatabase database) {
-        if (database.inTransaction()) {
-            // we won't run this inside another transaction.
-            return;
-        }
-        try {
-            // This method runs in a while loop because while changes are synced to db, another
-            // runnable may be skipped. If we cause it to skip, we need to do its work.
-            while (true) {
-                Lock closeLock = mDatabase.getCloseLock();
-                closeLock.lock();
-                try {
-                    // there is a potential race condition where another mSyncTriggers runnable
-                    // can start running right after we get the tables list to sync.
-                    final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
-                    if (tablesToSync == null) {
-                        return;
-                    }
-                    final int limit = tablesToSync.length;
-                    try {
-                        database.beginTransaction();
-                        for (int tableId = 0; tableId < limit; tableId++) {
-                            switch (tablesToSync[tableId]) {
-                                case ObservedTableTracker.ADD:
-                                    startTrackingTable(database, tableId);
-                                    break;
-                                case ObservedTableTracker.REMOVE:
-                                    stopTrackingTable(database, tableId);
-                                    break;
-                            }
-                        }
-                        database.setTransactionSuccessful();
-                    } finally {
-                        database.endTransaction();
-                    }
-                    mObservedTableTracker.onSyncCompleted();
-                } finally {
-                    closeLock.unlock();
-                }
-            }
-        } catch (IllegalStateException | SQLiteException exception) {
-            // may happen if db is closed. just log.
-            Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
-                    exception);
-        }
-    }
-
-    /**
-     * Called by RoomDatabase before each beginTransaction call.
-     * <p>
-     * It is important that pending trigger changes are applied to the database before any query
-     * runs. Otherwise, we may miss some changes.
-     * <p>
-     * This api should eventually be public.
-     */
-    void syncTriggers() {
-        if (!mDatabase.isOpen()) {
-            return;
-        }
-        syncTriggers(mDatabase.getOpenHelper().getWritableDatabase());
-    }
-
-    /**
-     * Wraps an observer and keeps the table information.
-     * <p>
-     * Internally table ids are used which may change from database to database so the table
-     * related information is kept here rather than in the Observer.
-     */
-    @SuppressWarnings("WeakerAccess")
-    static class ObserverWrapper {
-        final int[] mTableIds;
-        private final String[] mTableNames;
-        private final long[] mVersions;
-        final Observer mObserver;
-        private final Set<String> mSingleTableSet;
-
-        ObserverWrapper(Observer observer, int[] tableIds, String[] tableNames, long[] versions) {
-            mObserver = observer;
-            mTableIds = tableIds;
-            mTableNames = tableNames;
-            mVersions = versions;
-            if (tableIds.length == 1) {
-                ArraySet<String> set = new ArraySet<>();
-                set.add(mTableNames[0]);
-                mSingleTableSet = Collections.unmodifiableSet(set);
-            } else {
-                mSingleTableSet = null;
-            }
-        }
-
-        void checkForInvalidation(long[] versions) {
-            Set<String> invalidatedTables = null;
-            final int size = mTableIds.length;
-            for (int index = 0; index < size; index++) {
-                final int tableId = mTableIds[index];
-                final long newVersion = versions[tableId];
-                final long currentVersion = mVersions[index];
-                if (currentVersion < newVersion) {
-                    mVersions[index] = newVersion;
-                    if (size == 1) {
-                        // Optimization for a single-table observer
-                        invalidatedTables = mSingleTableSet;
-                    } else {
-                        if (invalidatedTables == null) {
-                            invalidatedTables = new ArraySet<>(size);
-                        }
-                        invalidatedTables.add(mTableNames[index]);
-                    }
-                }
-            }
-            if (invalidatedTables != null) {
-                mObserver.onInvalidated(invalidatedTables);
-            }
-        }
-    }
-
-    /**
-     * An observer that can listen for changes in the database.
-     */
-    public abstract static class Observer {
-        final String[] mTables;
-
-        /**
-         * Observes the given list of tables.
-         *
-         * @param firstTable The table name
-         * @param rest       More table names
-         */
-        @SuppressWarnings("unused")
-        protected Observer(@NonNull String firstTable, String... rest) {
-            mTables = Arrays.copyOf(rest, rest.length + 1);
-            mTables[rest.length] = firstTable;
-        }
-
-        /**
-         * Observes the given list of tables.
-         *
-         * @param tables The list of tables to observe for changes.
-         */
-        public Observer(@NonNull String[] tables) {
-            // copy tables in case user modifies them afterwards
-            mTables = Arrays.copyOf(tables, tables.length);
-        }
-
-        /**
-         * Called when one of the observed tables is invalidated in the database.
-         *
-         * @param tables A set of invalidated tables. This is useful when the observer targets
-         *               multiple tables and want to know which table is invalidated.
-         */
-        public abstract void onInvalidated(@NonNull Set<String> tables);
-    }
-
-
-    /**
-     * Keeps a list of tables we should observe. Invalidation tracker lazily syncs this list w/
-     * triggers in the database.
-     * <p>
-     * This class is thread safe
-     */
-    static class ObservedTableTracker {
-        static final int NO_OP = 0; // don't change trigger state for this table
-        static final int ADD = 1; // add triggers for this table
-        static final int REMOVE = 2; // remove triggers for this table
-
-        // number of observers per table
-        final long[] mTableObservers;
-        // trigger state for each table at last sync
-        // this field is updated when syncAndGet is called.
-        final boolean[] mTriggerStates;
-        // when sync is called, this field is returned. It includes actions as ADD, REMOVE, NO_OP
-        final int[] mTriggerStateChanges;
-
-        boolean mNeedsSync;
-
-        /**
-         * After we return non-null value from getTablesToSync, we expect a onSyncCompleted before
-         * returning any non-null value from getTablesToSync.
-         * This allows us to workaround any multi-threaded state syncing issues.
-         */
-        boolean mPendingSync;
-
-        ObservedTableTracker(int tableCount) {
-            mTableObservers = new long[tableCount];
-            mTriggerStates = new boolean[tableCount];
-            mTriggerStateChanges = new int[tableCount];
-            Arrays.fill(mTableObservers, 0);
-            Arrays.fill(mTriggerStates, false);
-        }
-
-        /**
-         * @return true if # of triggers is affected.
-         */
-        boolean onAdded(int... tableIds) {
-            boolean needTriggerSync = false;
-            synchronized (this) {
-                for (int tableId : tableIds) {
-                    final long prevObserverCount = mTableObservers[tableId];
-                    mTableObservers[tableId] = prevObserverCount + 1;
-                    if (prevObserverCount == 0) {
-                        mNeedsSync = true;
-                        needTriggerSync = true;
-                    }
-                }
-            }
-            return needTriggerSync;
-        }
-
-        /**
-         * @return true if # of triggers is affected.
-         */
-        boolean onRemoved(int... tableIds) {
-            boolean needTriggerSync = false;
-            synchronized (this) {
-                for (int tableId : tableIds) {
-                    final long prevObserverCount = mTableObservers[tableId];
-                    mTableObservers[tableId] = prevObserverCount - 1;
-                    if (prevObserverCount == 1) {
-                        mNeedsSync = true;
-                        needTriggerSync = true;
-                    }
-                }
-            }
-            return needTriggerSync;
-        }
-
-        /**
-         * If this returns non-null, you must call onSyncCompleted.
-         *
-         * @return int[] An int array where the index for each tableId has the action for that
-         * table.
-         */
-        @Nullable
-        int[] getTablesToSync() {
-            synchronized (this) {
-                if (!mNeedsSync || mPendingSync) {
-                    return null;
-                }
-                final int tableCount = mTableObservers.length;
-                for (int i = 0; i < tableCount; i++) {
-                    final boolean newState = mTableObservers[i] > 0;
-                    if (newState != mTriggerStates[i]) {
-                        mTriggerStateChanges[i] = newState ? ADD : REMOVE;
-                    } else {
-                        mTriggerStateChanges[i] = NO_OP;
-                    }
-                    mTriggerStates[i] = newState;
-                }
-                mPendingSync = true;
-                mNeedsSync = false;
-                return mTriggerStateChanges;
-            }
-        }
-
-        /**
-         * if getTablesToSync returned non-null, the called should call onSyncCompleted once it
-         * is done.
-         */
-        void onSyncCompleted() {
-            synchronized (this) {
-                mPendingSync = false;
-            }
-        }
-    }
-
-    /**
-     * An Observer wrapper that keeps a weak reference to the given object.
-     * <p>
-     * This class with automatically unsubscribe when the wrapped observer goes out of memory.
-     */
-    static class WeakObserver extends Observer {
-        final InvalidationTracker mTracker;
-        final WeakReference<Observer> mDelegateRef;
-
-        WeakObserver(InvalidationTracker tracker, Observer delegate) {
-            super(delegate.mTables);
-            mTracker = tracker;
-            mDelegateRef = new WeakReference<>(delegate);
-        }
-
-        @Override
-        public void onInvalidated(@NonNull Set<String> tables) {
-            final Observer observer = mDelegateRef.get();
-            if (observer == null) {
-                mTracker.removeObserver(this);
-            } else {
-                observer.onInvalidated(tables);
-            }
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/Room.java b/room/runtime/src/main/java/android/arch/persistence/room/Room.java
deleted file mode 100644
index 9b168fc..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/Room.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-
-/**
- * Utility class for Room.
- */
-@SuppressWarnings("unused")
-public class Room {
-    static final String LOG_TAG = "ROOM";
-    /**
-     * The master table where room keeps its metadata information.
-     */
-    public static final String MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME;
-    private static final String CURSOR_CONV_SUFFIX = "_CursorConverter";
-
-    /**
-     * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
-     * should keep a reference to it and re-use it.
-     *
-     * @param context The context for the database. This is usually the Application context.
-     * @param klass   The abstract class which is annotated with {@link Database} and extends
-     *                {@link RoomDatabase}.
-     * @param name    The name of the database file.
-     * @param <T>     The type of the database class.
-     * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
-     */
-    @SuppressWarnings("WeakerAccess")
-    @NonNull
-    public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
-            @NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
-        //noinspection ConstantConditions
-        if (name == null || name.trim().length() == 0) {
-            throw new IllegalArgumentException("Cannot build a database with null or empty name."
-                    + " If you are trying to create an in memory database, use Room"
-                    + ".inMemoryDatabaseBuilder");
-        }
-        return new RoomDatabase.Builder<>(context, klass, name);
-    }
-
-    /**
-     * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
-     * database disappears when the process is killed.
-     * Once a database is built, you should keep a reference to it and re-use it.
-     *
-     * @param context The context for the database. This is usually the Application context.
-     * @param klass   The abstract class which is annotated with {@link Database} and extends
-     *                {@link RoomDatabase}.
-     * @param <T>     The type of the database class.
-     * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
-     */
-    @NonNull
-    public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
-            @NonNull Context context, @NonNull Class<T> klass) {
-        return new RoomDatabase.Builder<>(context, klass, null);
-    }
-
-    @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
-    @NonNull
-    static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
-        final String fullPackage = klass.getPackage().getName();
-        String name = klass.getCanonicalName();
-        final String postPackageName = fullPackage.isEmpty()
-                ? name
-                : (name.substring(fullPackage.length() + 1));
-        final String implName = postPackageName.replace('.', '_') + suffix;
-        //noinspection TryWithIdenticalCatches
-        try {
-
-            @SuppressWarnings("unchecked")
-            final Class<T> aClass = (Class<T>) Class.forName(
-                    fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
-            return aClass.newInstance();
-        } catch (ClassNotFoundException e) {
-            throw new RuntimeException("cannot find implementation for "
-                    + klass.getCanonicalName() + ". " + implName + " does not exist");
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Cannot access the constructor"
-                    + klass.getCanonicalName());
-        } catch (InstantiationException e) {
-            throw new RuntimeException("Failed to create an instance of "
-                    + klass.getCanonicalName());
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
deleted file mode 100644
index 90131ed..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomDatabase.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.persistence.db.SimpleSQLiteQuery;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
-import android.arch.persistence.room.migration.Migration;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Build;
-import android.support.annotation.CallSuper;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-import android.support.v4.app.ActivityManagerCompat;
-import android.support.v4.util.SparseArrayCompat;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Base class for all Room databases. All classes that are annotated with {@link Database} must
- * extend this class.
- * <p>
- * RoomDatabase provides direct access to the underlying database implementation but you should
- * prefer using {@link Dao} classes.
- *
- * @see Database
- */
-//@SuppressWarnings({"unused", "WeakerAccess"})
-public abstract class RoomDatabase {
-    private static final String DB_IMPL_SUFFIX = "_Impl";
-    /**
-     * Unfortunately, we cannot read this value so we are only setting it to the SQLite default.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static final int MAX_BIND_PARAMETER_CNT = 999;
-    // set by the generated open helper.
-    protected volatile SupportSQLiteDatabase mDatabase;
-    private SupportSQLiteOpenHelper mOpenHelper;
-    private final InvalidationTracker mInvalidationTracker;
-    private boolean mAllowMainThreadQueries;
-    boolean mWriteAheadLoggingEnabled;
-
-    @Nullable
-    protected List<Callback> mCallbacks;
-
-    private final ReentrantLock mCloseLock = new ReentrantLock();
-
-    /**
-     * {@link InvalidationTracker} uses this lock to prevent the database from closing while it is
-     * querying database updates.
-     *
-     * @return The lock for {@link #close()}.
-     */
-    Lock getCloseLock() {
-        return mCloseLock;
-    }
-
-    /**
-     * Creates a RoomDatabase.
-     * <p>
-     * You cannot create an instance of a database, instead, you should acquire it via
-     * {@link Room#databaseBuilder(Context, Class, String)} or
-     * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
-     */
-    public RoomDatabase() {
-        mInvalidationTracker = createInvalidationTracker();
-    }
-
-    /**
-     * Called by {@link Room} when it is initialized.
-     *
-     * @param configuration The database configuration.
-     */
-    @CallSuper
-    public void init(@NonNull DatabaseConfiguration configuration) {
-        mOpenHelper = createOpenHelper(configuration);
-        boolean wal = false;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
-            mOpenHelper.setWriteAheadLoggingEnabled(wal);
-        }
-        mCallbacks = configuration.callbacks;
-        mAllowMainThreadQueries = configuration.allowMainThreadQueries;
-        mWriteAheadLoggingEnabled = wal;
-    }
-
-    /**
-     * Returns the SQLite open helper used by this database.
-     *
-     * @return The SQLite open helper used by this database.
-     */
-    @NonNull
-    public SupportSQLiteOpenHelper getOpenHelper() {
-        return mOpenHelper;
-    }
-
-    /**
-     * Creates the open helper to access the database. Generated class already implements this
-     * method.
-     * Note that this method is called when the RoomDatabase is initialized.
-     *
-     * @param config The configuration of the Room database.
-     * @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
-     */
-    @NonNull
-    protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
-
-    /**
-     * Called when the RoomDatabase is created.
-     * <p>
-     * This is already implemented by the generated code.
-     *
-     * @return Creates a new InvalidationTracker.
-     */
-    @NonNull
-    protected abstract InvalidationTracker createInvalidationTracker();
-
-    /**
-     * Deletes all rows from all the tables that are registered to this database as
-     * {@link Database#entities()}.
-     * <p>
-     * This does NOT reset the auto-increment value generated by {@link PrimaryKey#autoGenerate()}.
-     */
-    @WorkerThread
-    public abstract void clearAllTables();
-
-    /**
-     * Returns true if database connection is open and initialized.
-     *
-     * @return true if the database connection is open, false otherwise.
-     */
-    public boolean isOpen() {
-        final SupportSQLiteDatabase db = mDatabase;
-        return db != null && db.isOpen();
-    }
-
-    /**
-     * Closes the database if it is already open.
-     */
-    public void close() {
-        if (isOpen()) {
-            try {
-                mCloseLock.lock();
-                mOpenHelper.close();
-            } finally {
-                mCloseLock.unlock();
-            }
-        }
-    }
-
-    /**
-     * Asserts that we are not on the main thread.
-     *
-     * @hide
-     */
-    @SuppressWarnings("WeakerAccess")
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    // used in generated code
-    public void assertNotMainThread() {
-        if (mAllowMainThreadQueries) {
-            return;
-        }
-        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.");
-        }
-    }
-
-    // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which
-    // methods we are using and also helps unit tests to mock this class without mocking
-    // all SQLite database methods.
-
-    /**
-     * Convenience method to query the database with arguments.
-     *
-     * @param query The sql query
-     * @param args The bind arguments for the placeholders in the query
-     *
-     * @return A Cursor obtained by running the given query in the Room database.
-     */
-    public Cursor query(String query, @Nullable Object[] args) {
-        return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
-    }
-
-    /**
-     * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
-     *
-     * @param query The Query which includes the SQL and a bind callback for bind arguments.
-     * @return Result of the query.
-     */
-    public Cursor query(SupportSQLiteQuery query) {
-        assertNotMainThread();
-        return mOpenHelper.getWritableDatabase().query(query);
-    }
-
-    /**
-     * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
-     *
-     * @param sql The query to compile.
-     * @return The compiled query.
-     */
-    public SupportSQLiteStatement compileStatement(@NonNull String sql) {
-        assertNotMainThread();
-        return mOpenHelper.getWritableDatabase().compileStatement(sql);
-    }
-
-    /**
-     * Wrapper for {@link SupportSQLiteDatabase#beginTransaction()}.
-     */
-    public void beginTransaction() {
-        assertNotMainThread();
-        SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
-        mInvalidationTracker.syncTriggers(database);
-        database.beginTransaction();
-    }
-
-    /**
-     * Wrapper for {@link SupportSQLiteDatabase#endTransaction()}.
-     */
-    public void endTransaction() {
-        mOpenHelper.getWritableDatabase().endTransaction();
-        if (!inTransaction()) {
-            // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
-            // endTransaction call to do it.
-            mInvalidationTracker.refreshVersionsAsync();
-        }
-    }
-
-    /**
-     * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
-     */
-    public void setTransactionSuccessful() {
-        mOpenHelper.getWritableDatabase().setTransactionSuccessful();
-    }
-
-    /**
-     * Executes the specified {@link Runnable} in a database transaction. The transaction will be
-     * marked as successful unless an exception is thrown in the {@link Runnable}.
-     *
-     * @param body The piece of code to execute.
-     */
-    public void runInTransaction(@NonNull Runnable body) {
-        beginTransaction();
-        try {
-            body.run();
-            setTransactionSuccessful();
-        } finally {
-            endTransaction();
-        }
-    }
-
-    /**
-     * Executes the specified {@link Callable} in a database transaction. The transaction will be
-     * marked as successful unless an exception is thrown in the {@link Callable}.
-     *
-     * @param body The piece of code to execute.
-     * @param <V>  The type of the return value.
-     * @return The value returned from the {@link Callable}.
-     */
-    public <V> V runInTransaction(@NonNull Callable<V> body) {
-        beginTransaction();
-        try {
-            V result = body.call();
-            setTransactionSuccessful();
-            return result;
-        } catch (RuntimeException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new RuntimeException("Exception in transaction", e);
-        } finally {
-            endTransaction();
-        }
-    }
-
-    /**
-     * Called by the generated code when database is open.
-     * <p>
-     * You should never call this method manually.
-     *
-     * @param db The database instance.
-     */
-    protected void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase db) {
-        mInvalidationTracker.internalInit(db);
-    }
-
-    /**
-     * Returns the invalidation tracker for this database.
-     * <p>
-     * You can use the invalidation tracker to get notified when certain tables in the database
-     * are modified.
-     *
-     * @return The invalidation tracker for the database.
-     */
-    @NonNull
-    public InvalidationTracker getInvalidationTracker() {
-        return mInvalidationTracker;
-    }
-
-    /**
-     * Returns true if current thread is in a transaction.
-     *
-     * @return True if there is an active transaction in current thread, false otherwise.
-     * @see SupportSQLiteDatabase#inTransaction()
-     */
-    @SuppressWarnings("WeakerAccess")
-    public boolean inTransaction() {
-        return mOpenHelper.getWritableDatabase().inTransaction();
-    }
-
-    /**
-     * Journal modes for SQLite database.
-     *
-     * @see RoomDatabase.Builder#setJournalMode(JournalMode)
-     */
-    public enum JournalMode {
-
-        /**
-         * Let Room choose the journal mode. This is the default value when no explicit value is
-         * specified.
-         * <p>
-         * The actual value will be {@link #TRUNCATE} when the device runs API Level lower than 16
-         * or it is a low-RAM device. Otherwise, {@link #WRITE_AHEAD_LOGGING} will be used.
-         */
-        AUTOMATIC,
-
-        /**
-         * Truncate journal mode.
-         */
-        TRUNCATE,
-
-        /**
-         * Write-Ahead Logging mode.
-         */
-        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-        WRITE_AHEAD_LOGGING;
-
-        /**
-         * Resolves {@link #AUTOMATIC} to either {@link #TRUNCATE} or
-         * {@link #WRITE_AHEAD_LOGGING}.
-         */
-        @SuppressLint("NewApi")
-        JournalMode resolve(Context context) {
-            if (this != AUTOMATIC) {
-                return this;
-            }
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-                ActivityManager manager = (ActivityManager)
-                        context.getSystemService(Context.ACTIVITY_SERVICE);
-                if (manager != null && !ActivityManagerCompat.isLowRamDevice(manager)) {
-                    return WRITE_AHEAD_LOGGING;
-                }
-            }
-            return TRUNCATE;
-        }
-    }
-
-    /**
-     * Builder for RoomDatabase.
-     *
-     * @param <T> The type of the abstract database class.
-     */
-    public static class Builder<T extends RoomDatabase> {
-        private final Class<T> mDatabaseClass;
-        private final String mName;
-        private final Context mContext;
-        private ArrayList<Callback> mCallbacks;
-
-        private SupportSQLiteOpenHelper.Factory mFactory;
-        private boolean mAllowMainThreadQueries;
-        private JournalMode mJournalMode;
-        private boolean mRequireMigration;
-        /**
-         * Migrations, mapped by from-to pairs.
-         */
-        private final MigrationContainer mMigrationContainer;
-        private Set<Integer> mMigrationsNotRequiredFrom;
-        /**
-         * Keeps track of {@link Migration#startVersion}s and {@link Migration#endVersion}s added in
-         * {@link #addMigrations(Migration...)} for later validation that makes those versions don't
-         * match any versions passed to {@link #fallbackToDestructiveMigrationFrom(Integer...)}.
-         */
-        private Set<Integer> mMigrationStartAndEndVersions;
-
-        Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
-            mContext = context;
-            mDatabaseClass = klass;
-            mName = name;
-            mJournalMode = JournalMode.AUTOMATIC;
-            mRequireMigration = true;
-            mMigrationContainer = new MigrationContainer();
-        }
-
-        /**
-         * Sets the database factory. If not set, it defaults to
-         * {@link FrameworkSQLiteOpenHelperFactory}.
-         *
-         * @param factory The factory to use to access the database.
-         * @return this
-         */
-        @NonNull
-        public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
-            mFactory = factory;
-            return this;
-        }
-
-        /**
-         * Adds a migration to the builder.
-         * <p>
-         * Each Migration has a start and end versions and Room runs these migrations to bring the
-         * database to the latest version.
-         * <p>
-         * If a migration item is missing between current version and the latest version, Room
-         * will clear the database and recreate so even if you have no changes between 2 versions,
-         * you should still provide a Migration object to the builder.
-         * <p>
-         * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
-         * going version 3 to 5 without going to version 4). If Room opens a database at version
-         * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
-         * 3 to 5 instead of 3 to 4 and 4 to 5.
-         *
-         * @param migrations The migration object that can modify the database and to the necessary
-         *                   changes.
-         * @return this
-         */
-        @NonNull
-        public Builder<T> addMigrations(@NonNull  Migration... migrations) {
-            if (mMigrationStartAndEndVersions == null) {
-                mMigrationStartAndEndVersions = new HashSet<>();
-            }
-            for (Migration migration: migrations) {
-                mMigrationStartAndEndVersions.add(migration.startVersion);
-                mMigrationStartAndEndVersions.add(migration.endVersion);
-            }
-
-            mMigrationContainer.addMigrations(migrations);
-            return this;
-        }
-
-        /**
-         * Disables the main thread query check for Room.
-         * <p>
-         * Room ensures that Database is never accessed on the main thread because it may lock the
-         * main thread and trigger an ANR. If you need to access the database from the main thread,
-         * you should always use async alternatives or manually move the call to a background
-         * thread.
-         * <p>
-         * You may want to turn this check off for testing.
-         *
-         * @return this
-         */
-        @NonNull
-        public Builder<T> allowMainThreadQueries() {
-            mAllowMainThreadQueries = true;
-            return this;
-        }
-
-        /**
-         * Sets the journal mode for this database.
-         *
-         * <p>
-         * This value is ignored if the builder is initialized with
-         * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
-         * <p>
-         * The journal mode should be consistent across multiple instances of
-         * {@link RoomDatabase} for a single SQLite database file.
-         * <p>
-         * The default value is {@link JournalMode#AUTOMATIC}.
-         *
-         * @param journalMode The journal mode.
-         * @return this
-         */
-        @NonNull
-        public Builder<T> setJournalMode(@NonNull JournalMode journalMode) {
-            mJournalMode = journalMode;
-            return this;
-        }
-
-        /**
-         * 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>
-         * If it cannot find the set of {@link Migration}s that will bring the database to the
-         * current version, it will throw an {@link IllegalStateException}.
-         * <p>
-         * You can call this method to change this behavior to re-create the database instead of
-         * crashing.
-         * <p>
-         * Note that this will delete all of the data in the database tables managed by Room.
-         *
-         * @return this
-         */
-        @NonNull
-        public Builder<T> fallbackToDestructiveMigration() {
-            mRequireMigration = false;
-            return this;
-        }
-
-        /**
-         * Informs Room that it is allowed to destructively recreate database tables from specific
-         * starting schema versions.
-         * <p>
-         * This functionality is the same as that provided by
-         * {@link #fallbackToDestructiveMigration()}, except that this method allows the
-         * specification of a set of schema versions for which destructive recreation is allowed.
-         * <p>
-         * Using this method is preferable to {@link #fallbackToDestructiveMigration()} if you want
-         * to allow destructive migrations from some schema versions while still taking advantage
-         * of exceptions being thrown due to unintentionally missing migrations.
-         * <p>
-         * Note: No versions passed to this method may also exist as either starting or ending
-         * versions in the {@link Migration}s provided to {@link #addMigrations(Migration...)}. If a
-         * version passed to this method is found as a starting or ending version in a Migration, an
-         * exception will be thrown.
-         *
-         * @param startVersions The set of schema versions from which Room should use a destructive
-         *                      migration.
-         * @return this
-         */
-        @NonNull
-        public Builder<T> fallbackToDestructiveMigrationFrom(Integer... startVersions) {
-            if (mMigrationsNotRequiredFrom == null) {
-                mMigrationsNotRequiredFrom = new HashSet<>();
-            }
-            Collections.addAll(mMigrationsNotRequiredFrom, startVersions);
-            return this;
-        }
-
-        /**
-         * Adds a {@link Callback} to this database.
-         *
-         * @param callback The callback.
-         * @return this
-         */
-        @NonNull
-        public Builder<T> addCallback(@NonNull Callback callback) {
-            if (mCallbacks == null) {
-                mCallbacks = new ArrayList<>();
-            }
-            mCallbacks.add(callback);
-            return this;
-        }
-
-        /**
-         * Creates the databases and initializes it.
-         * <p>
-         * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
-         * triggers.
-         *
-         * @return A new database instance.
-         */
-        @NonNull
-        public T build() {
-            //noinspection ConstantConditions
-            if (mContext == null) {
-                throw new IllegalArgumentException("Cannot provide null context for the database.");
-            }
-            //noinspection ConstantConditions
-            if (mDatabaseClass == null) {
-                throw new IllegalArgumentException("Must provide an abstract class that"
-                        + " extends RoomDatabase");
-            }
-
-            if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
-                for (Integer version : mMigrationStartAndEndVersions) {
-                    if (mMigrationsNotRequiredFrom.contains(version)) {
-                        throw new IllegalArgumentException(
-                                "Inconsistency detected. A Migration was supplied to "
-                                        + "addMigration(Migration... migrations) that has a start "
-                                        + "or end version equal to a start version supplied to "
-                                        + "fallbackToDestructiveMigrationFrom(Integer ... "
-                                        + "startVersions). Start version: "
-                                        + version);
-                    }
-                }
-            }
-
-            if (mFactory == null) {
-                mFactory = new FrameworkSQLiteOpenHelperFactory();
-            }
-            DatabaseConfiguration configuration =
-                    new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
-                            mCallbacks, mAllowMainThreadQueries,
-                            mJournalMode.resolve(mContext),
-                            mRequireMigration, mMigrationsNotRequiredFrom);
-            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
-            db.init(configuration);
-            return db;
-        }
-    }
-
-    /**
-     * A container to hold migrations. It also allows querying its contents to find migrations
-     * between two versions.
-     */
-    public static class MigrationContainer {
-        private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
-                new SparseArrayCompat<>();
-
-        /**
-         * Adds the given migrations to the list of available migrations. If 2 migrations have the
-         * same start-end versions, the latter migration overrides the previous one.
-         *
-         * @param migrations List of available migrations.
-         */
-        public void addMigrations(@NonNull Migration... migrations) {
-            for (Migration migration : migrations) {
-                addMigration(migration);
-            }
-        }
-
-        private void addMigration(Migration migration) {
-            final int start = migration.startVersion;
-            final int end = migration.endVersion;
-            SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
-            if (targetMap == null) {
-                targetMap = new SparseArrayCompat<>();
-                mMigrations.put(start, targetMap);
-            }
-            Migration existing = targetMap.get(end);
-            if (existing != null) {
-                Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
-            }
-            targetMap.append(end, migration);
-        }
-
-        /**
-         * Finds the list of migrations that should be run to move from {@code start} version to
-         * {@code end} version.
-         *
-         * @param start The current database version
-         * @param end   The target database version
-         * @return An ordered list of {@link Migration} objects that should be run to migrate
-         * between the given versions. If a migration path cannot be found, returns {@code null}.
-         */
-        @SuppressWarnings("WeakerAccess")
-        @Nullable
-        public List<Migration> findMigrationPath(int start, int end) {
-            if (start == end) {
-                return Collections.emptyList();
-            }
-            boolean migrateUp = end > start;
-            List<Migration> result = new ArrayList<>();
-            return findUpMigrationPath(result, migrateUp, start, end);
-        }
-
-        private List<Migration> findUpMigrationPath(List<Migration> result, boolean upgrade,
-                int start, int end) {
-            final int searchDirection = upgrade ? -1 : 1;
-            while (upgrade ? start < end : start > end) {
-                SparseArrayCompat<Migration> targetNodes = mMigrations.get(start);
-                if (targetNodes == null) {
-                    return null;
-                }
-                // keys are ordered so we can start searching from one end of them.
-                final int size = targetNodes.size();
-                final int firstIndex;
-                final int lastIndex;
-
-                if (upgrade) {
-                    firstIndex = size - 1;
-                    lastIndex = -1;
-                } else {
-                    firstIndex = 0;
-                    lastIndex = size;
-                }
-                boolean found = false;
-                for (int i = firstIndex; i != lastIndex; i += searchDirection) {
-                    final int targetVersion = targetNodes.keyAt(i);
-                    final boolean shouldAddToPath;
-                    if (upgrade) {
-                        shouldAddToPath = targetVersion <= end && targetVersion > start;
-                    } else {
-                        shouldAddToPath = targetVersion >= end && targetVersion < start;
-                    }
-                    if (shouldAddToPath) {
-                        result.add(targetNodes.valueAt(i));
-                        start = targetVersion;
-                        found = true;
-                        break;
-                    }
-                }
-                if (!found) {
-                    return null;
-                }
-            }
-            return result;
-        }
-    }
-
-    /**
-     * Callback for {@link RoomDatabase}.
-     */
-    public abstract static class Callback {
-
-        /**
-         * Called when the database is created for the first time. This is called after all the
-         * tables are created.
-         *
-         * @param db The database.
-         */
-        public void onCreate(@NonNull SupportSQLiteDatabase db) {
-        }
-
-        /**
-         * Called when the database has been opened.
-         *
-         * @param db The database.
-         */
-        public void onOpen(@NonNull SupportSQLiteDatabase db) {
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java
deleted file mode 100644
index a921b9e..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomOpenHelper.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.persistence.db.SimpleSQLiteQuery;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.room.migration.Migration;
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.List;
-
-/**
- * An open helper that holds a reference to the configuration until the database is opened.
- *
- * @hide
- */
-@SuppressWarnings("unused")
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
-    @Nullable
-    private DatabaseConfiguration mConfiguration;
-    @NonNull
-    private final Delegate mDelegate;
-    @NonNull
-    private final String mIdentityHash;
-    /**
-     * Room v1 had a bug where the hash was not consistent if fields are reordered.
-     * The new has fixes it but we still need to accept the legacy hash.
-     */
-    @NonNull // b/64290754
-    private final String mLegacyHash;
-
-    public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
-            @NonNull String identityHash, @NonNull String legacyHash) {
-        super(delegate.version);
-        mConfiguration = configuration;
-        mDelegate = delegate;
-        mIdentityHash = identityHash;
-        mLegacyHash = legacyHash;
-    }
-
-    @Override
-    public void onConfigure(SupportSQLiteDatabase db) {
-        super.onConfigure(db);
-    }
-
-    @Override
-    public void onCreate(SupportSQLiteDatabase db) {
-        updateIdentity(db);
-        mDelegate.createAllTables(db);
-        mDelegate.onCreate(db);
-    }
-
-    @Override
-    public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
-        boolean migrated = false;
-        if (mConfiguration != null) {
-            List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
-                    oldVersion, newVersion);
-            if (migrations != null) {
-                for (Migration migration : migrations) {
-                    migration.migrate(db);
-                }
-                mDelegate.validateMigration(db);
-                updateIdentity(db);
-                migrated = true;
-            }
-        }
-        if (!migrated) {
-            if (mConfiguration != null && !mConfiguration.isMigrationRequiredFrom(oldVersion)) {
-                mDelegate.dropAllTables(db);
-                mDelegate.createAllTables(db);
-            } else {
-                throw new IllegalStateException("A migration from " + oldVersion + " to "
-                        + newVersion + " was required but not found. Please provide the "
-                        + "necessary Migration path via "
-                        + "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
-                        + "destructive migrations via one of the "
-                        + "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
-            }
-        }
-    }
-
-    @Override
-    public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
-        onUpgrade(db, oldVersion, newVersion);
-    }
-
-    @Override
-    public void onOpen(SupportSQLiteDatabase db) {
-        super.onOpen(db);
-        checkIdentity(db);
-        mDelegate.onOpen(db);
-        // there might be too many configurations etc, just clear it.
-        mConfiguration = null;
-    }
-
-    private void checkIdentity(SupportSQLiteDatabase db) {
-        String identityHash = "";
-        if (hasRoomMasterTable(db)) {
-            Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
-            //noinspection TryFinallyCanBeTryWithResources
-            try {
-                if (cursor.moveToFirst()) {
-                    identityHash = cursor.getString(0);
-                }
-            } finally {
-                cursor.close();
-            }
-        }
-        if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) {
-            throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
-                    + " you've changed schema but forgot to update the version number. You can"
-                    + " simply fix this by increasing the version number.");
-        }
-    }
-
-    private void updateIdentity(SupportSQLiteDatabase db) {
-        createMasterTableIfNotExists(db);
-        db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
-    }
-
-    private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
-        db.execSQL(RoomMasterTable.CREATE_QUERY);
-    }
-
-    private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) {
-        Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='"
-                + RoomMasterTable.TABLE_NAME + "'");
-        //noinspection TryFinallyCanBeTryWithResources
-        try {
-            return cursor.moveToFirst() && cursor.getInt(0) != 0;
-        } finally {
-            cursor.close();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @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);
-
-        protected abstract void onOpen(SupportSQLiteDatabase database);
-
-        protected abstract void onCreate(SupportSQLiteDatabase database);
-
-        /**
-         * Called after a migration run to validate database integrity.
-         *
-         * @param db The SQLite database.
-         */
-        protected abstract void validateMigration(SupportSQLiteDatabase db);
-    }
-
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java b/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java
deleted file mode 100644
index c4ff4bd..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/RoomSQLiteQuery.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.persistence.db.SupportSQLiteProgram;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.support.annotation.IntDef;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * This class is used as an intermediate place to keep binding arguments so that we can run
- * Cursor queries with correct types rather than passing everything as a string.
- * <p>
- * Because it is relatively a big object, they are pooled and must be released after each use.
- *
- * @hide
- */
-@SuppressWarnings("unused")
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram {
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    // Maximum number of queries we'll keep cached.
-    static final int POOL_LIMIT = 15;
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    // Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always
-    // clear the bigger queries (# of arguments).
-    static final int DESIRED_POOL_SIZE = 10;
-    private volatile String mQuery;
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    final long[] mLongBindings;
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    final double[] mDoubleBindings;
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    final String[] mStringBindings;
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    final byte[][] mBlobBindings;
-
-    @Binding
-    private final int[] mBindingTypes;
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    final int mCapacity;
-    // number of arguments in the query
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    int mArgCount;
-
-
-    @SuppressWarnings("WeakerAccess")
-    @VisibleForTesting
-    static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>();
-
-    /**
-     * Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery.
-     *
-     * @param supportSQLiteQuery The query to copy from
-     * @return A new query copied from the provided one.
-     */
-    public static RoomSQLiteQuery copyFrom(SupportSQLiteQuery supportSQLiteQuery) {
-        final RoomSQLiteQuery query = RoomSQLiteQuery.acquire(
-                supportSQLiteQuery.getSql(),
-                supportSQLiteQuery.getArgCount());
-        supportSQLiteQuery.bindTo(new SupportSQLiteProgram() {
-            @Override
-            public void bindNull(int index) {
-                query.bindNull(index);
-            }
-
-            @Override
-            public void bindLong(int index, long value) {
-                query.bindLong(index, value);
-            }
-
-            @Override
-            public void bindDouble(int index, double value) {
-                query.bindDouble(index, value);
-            }
-
-            @Override
-            public void bindString(int index, String value) {
-                query.bindString(index, value);
-            }
-
-            @Override
-            public void bindBlob(int index, byte[] value) {
-                query.bindBlob(index, value);
-            }
-
-            @Override
-            public void clearBindings() {
-                query.clearBindings();
-            }
-
-            @Override
-            public void close() {
-                // ignored.
-            }
-        });
-        return query;
-    }
-
-    /**
-     * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
-     * given query.
-     *
-     * @param query         The query to prepare
-     * @param argumentCount The number of query arguments
-     * @return A RoomSQLiteQuery that holds the given query and has space for the given number of
-     * arguments.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static RoomSQLiteQuery acquire(String query, int argumentCount) {
-        synchronized (sQueryPool) {
-            final Map.Entry<Integer, RoomSQLiteQuery> entry =
-                    sQueryPool.ceilingEntry(argumentCount);
-            if (entry != null) {
-                sQueryPool.remove(entry.getKey());
-                final RoomSQLiteQuery sqliteQuery = entry.getValue();
-                sqliteQuery.init(query, argumentCount);
-                return sqliteQuery;
-            }
-        }
-        RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount);
-        sqLiteQuery.init(query, argumentCount);
-        return sqLiteQuery;
-    }
-
-    private RoomSQLiteQuery(int capacity) {
-        mCapacity = capacity;
-        // because, 1 based indices... we don't want to offsets everything with 1 all the time.
-        int limit = capacity + 1;
-        //noinspection WrongConstant
-        mBindingTypes = new int[limit];
-        mLongBindings = new long[limit];
-        mDoubleBindings = new double[limit];
-        mStringBindings = new String[limit];
-        mBlobBindings = new byte[limit][];
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    void init(String query, int argCount) {
-        mQuery = query;
-        mArgCount = argCount;
-    }
-
-    /**
-     * Releases the query back to the pool.
-     * <p>
-     * After released, the statement might be returned when {@link #acquire(String, int)} is called
-     * so you should never re-use it after releasing.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public void release() {
-        synchronized (sQueryPool) {
-            sQueryPool.put(mCapacity, this);
-            prunePoolLocked();
-        }
-    }
-
-    private static void prunePoolLocked() {
-        if (sQueryPool.size() > POOL_LIMIT) {
-            int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE;
-            final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator();
-            while (toBeRemoved-- > 0) {
-                iterator.next();
-                iterator.remove();
-            }
-        }
-    }
-
-    @Override
-    public String getSql() {
-        return mQuery;
-    }
-
-    @Override
-    public int getArgCount() {
-        return mArgCount;
-    }
-
-    @Override
-    public void bindTo(SupportSQLiteProgram program) {
-        for (int index = 1; index <= mArgCount; index++) {
-            switch (mBindingTypes[index]) {
-                case NULL:
-                    program.bindNull(index);
-                    break;
-                case LONG:
-                    program.bindLong(index, mLongBindings[index]);
-                    break;
-                case DOUBLE:
-                    program.bindDouble(index, mDoubleBindings[index]);
-                    break;
-                case STRING:
-                    program.bindString(index, mStringBindings[index]);
-                    break;
-                case BLOB:
-                    program.bindBlob(index, mBlobBindings[index]);
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public void bindNull(int index) {
-        mBindingTypes[index] = NULL;
-    }
-
-    @Override
-    public void bindLong(int index, long value) {
-        mBindingTypes[index] = LONG;
-        mLongBindings[index] = value;
-    }
-
-    @Override
-    public void bindDouble(int index, double value) {
-        mBindingTypes[index] = DOUBLE;
-        mDoubleBindings[index] = value;
-    }
-
-    @Override
-    public void bindString(int index, String value) {
-        mBindingTypes[index] = STRING;
-        mStringBindings[index] = value;
-    }
-
-    @Override
-    public void bindBlob(int index, byte[] value) {
-        mBindingTypes[index] = BLOB;
-        mBlobBindings[index] = value;
-    }
-
-    @Override
-    public void close() {
-        // no-op. not calling release because it is internal API.
-    }
-
-    /**
-     * Copies arguments from another RoomSQLiteQuery into this query.
-     *
-     * @param other The other query, which holds the arguments to be copied.
-     */
-    public void copyArgumentsFrom(RoomSQLiteQuery other) {
-        int argCount = other.getArgCount() + 1; // +1 for the binding offsets
-        System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount);
-        System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount);
-        System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount);
-        System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount);
-        System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount);
-    }
-
-    @Override
-    public void clearBindings() {
-        Arrays.fill(mBindingTypes, NULL);
-        Arrays.fill(mStringBindings, null);
-        Arrays.fill(mBlobBindings, null);
-        mQuery = null;
-        // no need to clear others
-    }
-
-    private static final int NULL = 1;
-    private static final int LONG = 2;
-    private static final int DOUBLE = 3;
-    private static final int STRING = 4;
-    private static final int BLOB = 5;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({NULL, LONG, DOUBLE, STRING, BLOB})
-    @interface Binding {
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/SharedSQLiteStatement.java b/room/runtime/src/main/java/android/arch/persistence/room/SharedSQLiteStatement.java
deleted file mode 100644
index 6b1f8ea..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/SharedSQLiteStatement.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.support.annotation.RestrictTo;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Represents a prepared SQLite state that can be re-used multiple times.
- * <p>
- * This class is used by generated code. After it is used, {@code release} must be called so that
- * it can be used by other threads.
- * <p>
- * To avoid re-entry even within the same thread, this class allows only 1 time access to the shared
- * statement until it is released.
- *
- * @hide
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class SharedSQLiteStatement {
-    private final AtomicBoolean mLock = new AtomicBoolean(false);
-
-    private final RoomDatabase mDatabase;
-    private volatile SupportSQLiteStatement mStmt;
-
-    /**
-     * Creates an SQLite prepared statement that can be re-used across threads. If it is in use,
-     * it automatically creates a new one.
-     *
-     * @param database The database to create the statement in.
-     */
-    public SharedSQLiteStatement(RoomDatabase database) {
-        mDatabase = database;
-    }
-
-    /**
-     * Create the query.
-     *
-     * @return The SQL query to prepare.
-     */
-    protected abstract String createQuery();
-
-    protected void assertNotMainThread() {
-        mDatabase.assertNotMainThread();
-    }
-
-    private SupportSQLiteStatement createNewStatement() {
-        String query = createQuery();
-        return mDatabase.compileStatement(query);
-    }
-
-    private SupportSQLiteStatement getStmt(boolean canUseCached) {
-        final SupportSQLiteStatement stmt;
-        if (canUseCached) {
-            if (mStmt == null) {
-                mStmt = createNewStatement();
-            }
-            stmt = mStmt;
-        } else {
-            // it is in use, create a one off statement
-            stmt = createNewStatement();
-        }
-        return stmt;
-    }
-
-    /**
-     * Call this to get the statement. Must call {@link #release(SupportSQLiteStatement)} once done.
-     */
-    public SupportSQLiteStatement acquire() {
-        assertNotMainThread();
-        return getStmt(mLock.compareAndSet(false, true));
-    }
-
-    /**
-     * Must call this when statement will not be used anymore.
-     *
-     * @param statement The statement that was returned from acquire.
-     */
-    public void release(SupportSQLiteStatement statement) {
-        if (statement == mStmt) {
-            mLock.set(false);
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java b/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java
deleted file mode 100644
index d69ea0d..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.migration;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.support.annotation.NonNull;
-
-/**
- * Base class for a database migration.
- * <p>
- * Each migration can move between 2 versions that are defined by {@link #startVersion} and
- * {@link #endVersion}.
- * <p>
- * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
- * going version 3 to 5 without going to version 4). If Room opens a database at version
- * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
- * 3 to 5 instead of 3 to 4 and 4 to 5.
- * <p>
- * If there are not enough migrations provided to move from the current version to the latest
- * version, Room will clear the database and recreate so even if you have no changes between 2
- * versions, you should still provide a Migration object to the builder.
- */
-public abstract class Migration {
-    public final int startVersion;
-    public final int endVersion;
-
-    /**
-     * Creates a new migration between {@code startVersion} and {@code endVersion}.
-     *
-     * @param startVersion The start version of the database.
-     * @param endVersion The end version of the database after this migration is applied.
-     */
-    public Migration(int startVersion, int endVersion) {
-        this.startVersion = startVersion;
-        this.endVersion = endVersion;
-    }
-
-    /**
-     * Should run the necessary migrations.
-     * <p>
-     * This class cannot access any generated Dao in this method.
-     * <p>
-     * This method is already called inside a transaction and that transaction might actually be a
-     * composite transaction of all necessary {@code Migration}s.
-     *
-     * @param database The database instance
-     */
-    public abstract void migrate(@NonNull SupportSQLiteDatabase database);
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/package-info.java b/room/runtime/src/main/java/android/arch/persistence/room/package-info.java
deleted file mode 100644
index 1dafc1b..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/package-info.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Room is a Database Object Mapping library that makes it easy to access database on Android
- * applications.
- * <p>
- * Rather than hiding the detail of SQLite, Room tries to embrace them by providing convenient APIs
- * to query the database and also verify such queries at compile time. This allows you to access
- * the full power of SQLite while having the type safety provided by Java SQL query builders.
- * <p>
- * There are 3 major components in Room.
- * <ul>
- *     <li>{@link android.arch.persistence.room.Database Database}: This annotation marks a
- *     class as a database. It should be an abstract class that extends
- *     {@link android.arch.persistence.room.RoomDatabase RoomDatabase}. At runtime, you can acquire
- *     an instance of it via {@link android.arch.persistence.room.Room#databaseBuilder(
- *     android.content.Context,java.lang.Class, java.lang.String) Room.databaseBuilder} or
- *     {@link android.arch.persistence.room.Room#inMemoryDatabaseBuilder(android.content.Context,
- *     java.lang.Class) Room.inMemoryDatabaseBuilder}.
- *     <p>
- *         This class defines the list of entities and data access objects in the database. It is
- *         also the main access point for the underlying connection.
- *     </li>
- *     <li>{@link android.arch.persistence.room.Entity Entity}: This annotation marks a class as a
- *     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 (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
- *     responsible for defining the methods that access the database. The class that is annotated
- *     with {@link android.arch.persistence.room.Database Database} must have an abstract method
- *     that has 0 arguments and returns the class that is annotated with Dao. While generating the
- *     code at compile time, Room will generate an implementation of this class.
- *     <pre>
- *     Using Dao classes for database access rather than query builders or direct queries allows you
- *     to keep a separation between different components and easily mock the database access while
- *     testing your application.
- *     </li>
- * </ul>
- * Below is a sample of a simple database.
- * <pre>
- * // File: User.java
- * {@literal @}Entity
- * public class User {
- *   {@literal @}PrimaryKey
- *   private int uid;
- *   private String name;
- *   {@literal @}ColumnInfo(name = "last_name")
- *   private String lastName;
- *   // getters and setters are ignored for brevity but they are required for Room to work.
- * }
- * // File: UserDao.java
- * {@literal @}Dao
- * public interface UserDao {
- *   {@literal @}Query("SELECT * FROM user")
- *   List&lt;User&gt; loadAll();
- *   {@literal @}Query("SELECT * FROM user WHERE uid IN (:userIds)")
- *   List&lt;User&gt; loadAllByUserId(int... userIds);
- *   {@literal @}Query("SELECT * FROM user where name LIKE :first AND last_name LIKE :last LIMIT 1")
- *   User loadOneByNameAndLastName(String first, String last);
- *   {@literal @}Insert
- *   void insertAll(User... users);
- *   {@literal @}Delete
- *   void delete(User user);
- * }
- * // File: AppDatabase.java
- * {@literal @}Database(entities = {User.java})
- * public abstract class AppDatabase extends RoomDatabase {
- *   public abstract UserDao userDao();
- * }
- * </pre>
- * You can create an instance of {@code AppDatabase} as follows:
- * <pre>
- * AppDatabase db = Room
- *     .databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name")
- *     .build();
- * </pre>
- * Since Room verifies your queries at compile time, it also detects information about which tables
- * are accessed by the query or what columns are present in the response.
- * <p>
- * You can observe a particular table for changes using the
- * {@link android.arch.persistence.room.InvalidationTracker InvalidationTracker} class which you can
- * acquire via {@link android.arch.persistence.room.RoomDatabase#getInvalidationTracker()
- * RoomDatabase.getInvalidationTracker}.
- * <p>
- * For convenience, Room allows you to return {@link android.arch.lifecycle.LiveData
- * LiveData} from {@link android.arch.persistence.room.Query Query} methods. It will automatically
- * observe the related tables as long as the {@code LiveData} has active observers.
- * <pre>
- * // This live data will automatically dispatch changes as the database changes.
- * {@literal @}Query("SELECT * FROM user ORDER BY name LIMIT 5")
- * LiveData&lt;User&gt; loadFirstFiveUsers();
- * </pre>
- * <p>
- * You can also return arbitrary Java objects from your query results as long as the fields in the
- * object match the list of columns in the query response. This makes it very easy to write
- * applications that drive the UI from persistent storage.
- * <pre>
- * class IdAndFullName {
- *     public int uid;
- *     {@literal @}ColumnInfo(name = "full_name")
- *     public String fullName;
- * }
- * // DAO
- * {@literal @}Query("SELECT uid, name || lastName as full_name FROM user")
- * public IdAndFullName[] loadFullNames();
- * </pre>
- * If there is a mismatch between the query result and the POJO, Room will print a warning during
- * compilation.
- * <p>
- * Please see the documentation of individual classes for details.
- */
-package android.arch.persistence.room;
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/paging/LimitOffsetDataSource.java b/room/runtime/src/main/java/android/arch/persistence/room/paging/LimitOffsetDataSource.java
deleted file mode 100644
index 73777c4..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/paging/LimitOffsetDataSource.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.paging;
-
-import android.arch.paging.PositionalDataSource;
-import android.arch.persistence.db.SupportSQLiteQuery;
-import android.arch.persistence.room.InvalidationTracker;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.RoomSQLiteQuery;
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A simple data source implementation that uses Limit & Offset to page the query.
- * <p>
- * This is NOT the most efficient way to do paging on SQLite. It is
- * <a href="http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor">recommended</a> to use an indexed
- * ORDER BY statement but that requires a more complex API. This solution is technically equal to
- * receiving a {@link Cursor} from a large query but avoids the need to manually manage it, and
- * never returns inconsistent data if it is invalidated.
- *
- * @param <T> Data type returned by the data source.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class LimitOffsetDataSource<T> extends PositionalDataSource<T> {
-    private final RoomSQLiteQuery mSourceQuery;
-    private final String mCountQuery;
-    private final String mLimitOffsetQuery;
-    private final RoomDatabase mDb;
-    @SuppressWarnings("FieldCanBeLocal")
-    private final InvalidationTracker.Observer mObserver;
-    private final boolean mInTransaction;
-
-    protected LimitOffsetDataSource(RoomDatabase db, SupportSQLiteQuery query,
-            boolean inTransaction, String... tables) {
-        this(db, RoomSQLiteQuery.copyFrom(query), inTransaction, tables);
-    }
-
-    protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
-            boolean inTransaction, String... tables) {
-        mDb = db;
-        mSourceQuery = query;
-        mInTransaction = inTransaction;
-        mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
-        mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
-        mObserver = new InvalidationTracker.Observer(tables) {
-            @Override
-            public void onInvalidated(@NonNull Set<String> tables) {
-                invalidate();
-            }
-        };
-        db.getInvalidationTracker().addWeakObserver(mObserver);
-    }
-
-    /**
-     * Count number of rows query can return
-     */
-    @SuppressWarnings("WeakerAccess")
-    public int countItems() {
-        final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mCountQuery,
-                mSourceQuery.getArgCount());
-        sqLiteQuery.copyArgumentsFrom(mSourceQuery);
-        Cursor cursor = mDb.query(sqLiteQuery);
-        try {
-            if (cursor.moveToFirst()) {
-                return cursor.getInt(0);
-            }
-            return 0;
-        } finally {
-            cursor.close();
-            sqLiteQuery.release();
-        }
-    }
-
-    @Override
-    public boolean isInvalid() {
-        mDb.getInvalidationTracker().refreshVersionsSync();
-        return super.isInvalid();
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    protected abstract List<T> convertRows(Cursor cursor);
-
-    @Override
-    public void loadInitial(@NonNull LoadInitialParams params,
-            @NonNull LoadInitialCallback<T> callback) {
-        int totalCount = countItems();
-        if (totalCount == 0) {
-            callback.onResult(Collections.<T>emptyList(), 0, 0);
-            return;
-        }
-
-        // bound the size requested, based on known count
-        final int firstLoadPosition = computeInitialLoadPosition(params, totalCount);
-        final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);
-
-        List<T> list = loadRange(firstLoadPosition, firstLoadSize);
-        if (list != null && list.size() == firstLoadSize) {
-            callback.onResult(list, firstLoadPosition, totalCount);
-        } else {
-            // null list, or size doesn't match request - DB modified between count and load
-            invalidate();
-        }
-    }
-
-    @Override
-    public void loadRange(@NonNull LoadRangeParams params,
-            @NonNull LoadRangeCallback<T> callback) {
-        List<T> list = loadRange(params.startPosition, params.loadSize);
-        if (list != null) {
-            callback.onResult(list);
-        } else {
-            invalidate();
-        }
-    }
-
-    /**
-     * Return the rows from startPos to startPos + loadCount
-     */
-    @Nullable
-    public List<T> loadRange(int startPosition, int loadCount) {
-        final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mLimitOffsetQuery,
-                mSourceQuery.getArgCount() + 2);
-        sqLiteQuery.copyArgumentsFrom(mSourceQuery);
-        sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
-        sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
-        if (mInTransaction) {
-            mDb.beginTransaction();
-            Cursor cursor = null;
-            try {
-                cursor = mDb.query(sqLiteQuery);
-                List<T> rows = convertRows(cursor);
-                mDb.setTransactionSuccessful();
-                return rows;
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-                mDb.endTransaction();
-                sqLiteQuery.release();
-            }
-        } else {
-            Cursor cursor = mDb.query(sqLiteQuery);
-            //noinspection TryFinallyCanBeTryWithResources
-            try {
-                return convertRows(cursor);
-            } finally {
-                cursor.close();
-                sqLiteQuery.release();
-            }
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/util/StringUtil.java b/room/runtime/src/main/java/android/arch/persistence/room/util/StringUtil.java
deleted file mode 100644
index d01e3c5..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/util/StringUtil.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.util;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-
-/**
- * @hide
- *
- * String utilities for Room
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class StringUtil {
-
-    @SuppressWarnings("unused")
-    public static final String[] EMPTY_STRING_ARRAY = new String[0];
-    /**
-     * Returns a new StringBuilder to be used while producing SQL queries.
-     *
-     * @return A new or recycled StringBuilder
-     */
-    public static StringBuilder newStringBuilder() {
-        // TODO pool:
-        return new StringBuilder();
-    }
-
-    /**
-     * Adds bind variable placeholders (?) to the given string. Each placeholder is separated
-     * by a comma.
-     *
-     * @param builder The StringBuilder for the query
-     * @param count Number of placeholders
-     */
-    public static void appendPlaceholders(StringBuilder builder, int count) {
-        for (int i = 0; i < count; i++) {
-            builder.append("?");
-            if (i < count - 1) {
-                builder.append(",");
-            }
-        }
-    }
-    /**
-     * Splits a comma separated list of integers to integer list.
-     * <p>
-     * If an input is malformed, it is omitted from the result.
-     *
-     * @param input Comma separated list of integers.
-     * @return A List containing the integers or null if the input is null.
-     */
-    @Nullable
-    public static List<Integer> splitToIntList(@Nullable String input) {
-        if (input == null) {
-            return null;
-        }
-        List<Integer> result = new ArrayList<>();
-        StringTokenizer tokenizer = new StringTokenizer(input, ",");
-        while (tokenizer.hasMoreElements()) {
-            final String item = tokenizer.nextToken();
-            try {
-                result.add(Integer.parseInt(item));
-            } catch (NumberFormatException ex) {
-                Log.e("ROOM", "Malformed integer list", ex);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Joins the given list of integers into a comma separated list.
-     *
-     * @param input The list of integers.
-     * @return Comma separated string composed of integers in the list. If the list is null, return
-     * value is null.
-     */
-    @Nullable
-    public static String joinIntoString(@Nullable List<Integer> input) {
-        if (input == null) {
-            return null;
-        }
-
-        final int size = input.size();
-        if (size == 0) {
-            return "";
-        }
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < size; i++) {
-            sb.append(Integer.toString(input.get(i)));
-            if (i < size - 1) {
-                sb.append(",");
-            }
-        }
-        return sb.toString();
-    }
-}
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java b/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java
deleted file mode 100644
index 19d9853..0000000
--- a/room/runtime/src/main/java/android/arch/persistence/room/util/TableInfo.java
+++ /dev/null
@@ -1,591 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.util;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.room.ColumnInfo;
-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;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-/**
- * A data class that holds the information about a table.
- * <p>
- * It directly maps to the result of {@code PRAGMA table_info(<table_name>)}. Check the
- * <a href="http://www.sqlite.org/pragma.html#pragma_table_info">PRAGMA table_info</a>
- * documentation for more details.
- * <p>
- * Even though SQLite column names are case insensitive, this class uses case sensitive matching.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources",
-        "SimplifiableIfStatement"})
-// if you change this class, you must change TableInfoWriter.kt
-public class TableInfo {
-    /**
-     * The table name.
-     */
-    public final String name;
-    /**
-     * Unmodifiable map of columns keyed by column name.
-     */
-    public final Map<String, Column> columns;
-
-    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,
-            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
-                + '}';
-    }
-
-    /**
-     * Reads the table information from the given database.
-     *
-     * @param database  The database to read the information from.
-     * @param tableName The table name.
-     * @return A TableInfo containing the schema information for the provided table name.
-     */
-    @SuppressWarnings("SameParameterValue")
-    public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
-        Map<String, Column> columns = readColumns(database, tableName);
-        Set<ForeignKey> foreignKeys = readForeignKeys(database, tableName);
-        Set<Index> indices = readIndices(database, tableName);
-        return new TableInfo(tableName, columns, foreignKeys, indices);
-    }
-
-    private static Set<ForeignKey> readForeignKeys(SupportSQLiteDatabase database,
-            String tableName) {
-        Set<ForeignKey> foreignKeys = new HashSet<>();
-        // this seems to return everything in order but it is not documented so better be safe
-        Cursor cursor = database.query("PRAGMA foreign_key_list(`" + tableName + "`)");
-        try {
-            final int idColumnIndex = cursor.getColumnIndex("id");
-            final int seqColumnIndex = cursor.getColumnIndex("seq");
-            final int tableColumnIndex = cursor.getColumnIndex("table");
-            final int onDeleteColumnIndex = cursor.getColumnIndex("on_delete");
-            final int onUpdateColumnIndex = cursor.getColumnIndex("on_update");
-
-            final List<ForeignKeyWithSequence> ordered = readForeignKeyFieldMappings(cursor);
-            final int count = cursor.getCount();
-            for (int position = 0; position < count; position++) {
-                cursor.moveToPosition(position);
-                final int seq = cursor.getInt(seqColumnIndex);
-                if (seq != 0) {
-                    continue;
-                }
-                final int id = cursor.getInt(idColumnIndex);
-                List<String> myColumns = new ArrayList<>();
-                List<String> refColumns = new ArrayList<>();
-                for (ForeignKeyWithSequence key : ordered) {
-                    if (key.mId == id) {
-                        myColumns.add(key.mFrom);
-                        refColumns.add(key.mTo);
-                    }
-                }
-                foreignKeys.add(new ForeignKey(
-                        cursor.getString(tableColumnIndex),
-                        cursor.getString(onDeleteColumnIndex),
-                        cursor.getString(onUpdateColumnIndex),
-                        myColumns,
-                        refColumns
-                ));
-            }
-        } finally {
-            cursor.close();
-        }
-        return foreignKeys;
-    }
-
-    private static List<ForeignKeyWithSequence> readForeignKeyFieldMappings(Cursor cursor) {
-        final int idColumnIndex = cursor.getColumnIndex("id");
-        final int seqColumnIndex = cursor.getColumnIndex("seq");
-        final int fromColumnIndex = cursor.getColumnIndex("from");
-        final int toColumnIndex = cursor.getColumnIndex("to");
-        final int count = cursor.getCount();
-        List<ForeignKeyWithSequence> result = new ArrayList<>();
-        for (int i = 0; i < count; i++) {
-            cursor.moveToPosition(i);
-            result.add(new ForeignKeyWithSequence(
-                    cursor.getInt(idColumnIndex),
-                    cursor.getInt(seqColumnIndex),
-                    cursor.getString(fromColumnIndex),
-                    cursor.getString(toColumnIndex)
-            ));
-        }
-        Collections.sort(result);
-        return result;
-    }
-
-    private static Map<String, Column> readColumns(SupportSQLiteDatabase database,
-            String tableName) {
-        Cursor cursor = database
-                .query("PRAGMA table_info(`" + tableName + "`)");
-        //noinspection TryFinallyCanBeTryWithResources
-        Map<String, Column> columns = new HashMap<>();
-        try {
-            if (cursor.getColumnCount() > 0) {
-                int nameIndex = cursor.getColumnIndex("name");
-                int typeIndex = cursor.getColumnIndex("type");
-                int notNullIndex = cursor.getColumnIndex("notnull");
-                int pkIndex = cursor.getColumnIndex("pk");
-
-                while (cursor.moveToNext()) {
-                    final String name = cursor.getString(nameIndex);
-                    final String type = cursor.getString(typeIndex);
-                    final boolean notNull = 0 != cursor.getInt(notNullIndex);
-                    final int primaryKeyPosition = cursor.getInt(pkIndex);
-                    columns.put(name, new Column(name, type, notNull, primaryKeyPosition));
-                }
-            }
-        } finally {
-            cursor.close();
-        }
-        return columns;
-    }
-
-    /**
-     * @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();
-        }
-    }
-
-    /**
-     * @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<>();
-
-            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();
-        }
-    }
-
-    /**
-     * Holds the information about a database column.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static class Column {
-        /**
-         * The column name.
-         */
-        public final String name;
-        /**
-         * The column type affinity.
-         */
-        public final String type;
-        /**
-         * The column type after it is normalized to one of the basic types according to
-         * https://www.sqlite.org/datatype3.html Section 3.1.
-         * <p>
-         * This is the value Room uses for equality check.
-         */
-        @ColumnInfo.SQLiteTypeAffinity
-        public final int affinity;
-        /**
-         * Whether or not the column can be NULL.
-         */
-        public final boolean notNull;
-        /**
-         * The position of the column in the list of primary keys, 0 if the column is not part
-         * of the primary key.
-         * <p>
-         * This information is only available in API 20+.
-         * <a href="https://www.sqlite.org/releaselog/3_7_16_2.html">(SQLite version 3.7.16.2)</a>
-         * On older platforms, it will be 1 if the column is part of the primary key and 0
-         * otherwise.
-         * <p>
-         * The {@link #equals(Object)} implementation handles this inconsistency based on
-         * API levels os if you are using a custom SQLite deployment, it may return false
-         * positives.
-         */
-        public final int primaryKeyPosition;
-
-        // if you change this constructor, you must change TableInfoWriter.kt
-        public Column(String name, String type, boolean notNull, int primaryKeyPosition) {
-            this.name = name;
-            this.type = type;
-            this.notNull = notNull;
-            this.primaryKeyPosition = primaryKeyPosition;
-            this.affinity = findAffinity(type);
-        }
-
-        /**
-         * Implements https://www.sqlite.org/datatype3.html section 3.1
-         *
-         * @param type The type that was given to the sqlite
-         * @return The normalized type which is one of the 5 known affinities
-         */
-        @ColumnInfo.SQLiteTypeAffinity
-        private static int findAffinity(@Nullable String type) {
-            if (type == null) {
-                return ColumnInfo.BLOB;
-            }
-            String uppercaseType = type.toUpperCase(Locale.US);
-            if (uppercaseType.contains("INT")) {
-                return ColumnInfo.INTEGER;
-            }
-            if (uppercaseType.contains("CHAR")
-                    || uppercaseType.contains("CLOB")
-                    || uppercaseType.contains("TEXT")) {
-                return ColumnInfo.TEXT;
-            }
-            if (uppercaseType.contains("BLOB")) {
-                return ColumnInfo.BLOB;
-            }
-            if (uppercaseType.contains("REAL")
-                    || uppercaseType.contains("FLOA")
-                    || uppercaseType.contains("DOUB")) {
-                return ColumnInfo.REAL;
-            }
-            // sqlite returns NUMERIC here but it is like a catch all. We already
-            // have UNDEFINED so it is better to use UNDEFINED for consistency.
-            return ColumnInfo.UNDEFINED;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            Column column = (Column) o;
-            if (Build.VERSION.SDK_INT >= 20) {
-                if (primaryKeyPosition != column.primaryKeyPosition) return false;
-            } else {
-                if (isPrimaryKey() != column.isPrimaryKey()) return false;
-            }
-
-            if (!name.equals(column.name)) return false;
-            //noinspection SimplifiableIfStatement
-            if (notNull != column.notNull) return false;
-            return affinity == column.affinity;
-        }
-
-        /**
-         * Returns whether this column is part of the primary key or not.
-         *
-         * @return True if this column is part of the primary key, false otherwise.
-         */
-        public boolean isPrimaryKey() {
-            return primaryKeyPosition > 0;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = name.hashCode();
-            result = 31 * result + affinity;
-            result = 31 * result + (notNull ? 1231 : 1237);
-            result = 31 * result + primaryKeyPosition;
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return "Column{"
-                    + "name='" + name + '\''
-                    + ", type='" + type + '\''
-                    + ", affinity='" + affinity + '\''
-                    + ", notNull=" + notNull
-                    + ", primaryKeyPosition=" + primaryKeyPosition
-                    + '}';
-        }
-    }
-
-    /**
-     * Holds the information about an SQLite foreign key
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static class ForeignKey {
-        @NonNull
-        public final String referenceTable;
-        @NonNull
-        public final String onDelete;
-        @NonNull
-        public final String onUpdate;
-        @NonNull
-        public final List<String> columnNames;
-        @NonNull
-        public final List<String> referenceColumnNames;
-
-        public ForeignKey(@NonNull String referenceTable, @NonNull String onDelete,
-                @NonNull String onUpdate,
-                @NonNull List<String> columnNames, @NonNull List<String> referenceColumnNames) {
-            this.referenceTable = referenceTable;
-            this.onDelete = onDelete;
-            this.onUpdate = onUpdate;
-            this.columnNames = Collections.unmodifiableList(columnNames);
-            this.referenceColumnNames = Collections.unmodifiableList(referenceColumnNames);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            ForeignKey that = (ForeignKey) o;
-
-            if (!referenceTable.equals(that.referenceTable)) return false;
-            if (!onDelete.equals(that.onDelete)) return false;
-            if (!onUpdate.equals(that.onUpdate)) return false;
-            //noinspection SimplifiableIfStatement
-            if (!columnNames.equals(that.columnNames)) return false;
-            return referenceColumnNames.equals(that.referenceColumnNames);
-        }
-
-        @Override
-        public int hashCode() {
-            int result = referenceTable.hashCode();
-            result = 31 * result + onDelete.hashCode();
-            result = 31 * result + onUpdate.hashCode();
-            result = 31 * result + columnNames.hashCode();
-            result = 31 * result + referenceColumnNames.hashCode();
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return "ForeignKey{"
-                    + "referenceTable='" + referenceTable + '\''
-                    + ", onDelete='" + onDelete + '\''
-                    + ", onUpdate='" + onUpdate + '\''
-                    + ", columnNames=" + columnNames
-                    + ", referenceColumnNames=" + referenceColumnNames
-                    + '}';
-        }
-    }
-
-    /**
-     * Temporary data holder for a foreign key row in the pragma result. We need this to ensure
-     * sorting in the generated foreign key object.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    static class ForeignKeyWithSequence implements Comparable<ForeignKeyWithSequence> {
-        final int mId;
-        final int mSequence;
-        final String mFrom;
-        final String mTo;
-
-        ForeignKeyWithSequence(int id, int sequence, String from, String to) {
-            mId = id;
-            mSequence = sequence;
-            mFrom = from;
-            mTo = to;
-        }
-
-        @Override
-        public int compareTo(@NonNull ForeignKeyWithSequence o) {
-            final int idCmp = mId - o.mId;
-            if (idCmp == 0) {
-                return mSequence - o.mSequence;
-            } else {
-                return idCmp;
-            }
-        }
-    }
-
-    /**
-     * 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
-                    + '}';
-        }
-    }
-}
diff --git a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
new file mode 100644
index 0000000..8bc1d4f
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Configuration class for a {@link RoomDatabase}.
+ */
+@SuppressWarnings("WeakerAccess")
+public class DatabaseConfiguration {
+    /**
+     * The factory to use to access the database.
+     */
+    @NonNull
+    public final SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    /**
+     * The context to use while connecting to the database.
+     */
+    @NonNull
+    public final Context context;
+    /**
+     * The name of the database file or null if it is an in-memory database.
+     */
+    @Nullable
+    public final String name;
+
+    /**
+     * Collection of available migrations.
+     */
+    @NonNull
+    public final RoomDatabase.MigrationContainer migrationContainer;
+
+    @Nullable
+    public final List<RoomDatabase.Callback> callbacks;
+
+    /**
+     * Whether Room should throw an exception for queries run on the main thread.
+     */
+    public final boolean allowMainThreadQueries;
+
+    /**
+     * The journal mode for this database.
+     */
+    public final RoomDatabase.JournalMode journalMode;
+
+    /**
+     * If true, Room should crash if a migration is missing.
+     */
+    public final boolean requireMigration;
+
+    /**
+     * The collection of schema versions from which migrations aren't required.
+     */
+    private final Set<Integer> mMigrationNotRequiredFrom;
+
+    /**
+     * Creates a database configuration with the given values.
+     *
+     * @param context The application context.
+     * @param name Name of the database, can be null if it is in memory.
+     * @param sqliteOpenHelperFactory The open helper factory to use.
+     * @param migrationContainer The migration container for migrations.
+     * @param callbacks The list of callbacks for database events.
+     * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
+     * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
+     * @param requireMigration True if Room should require a valid migration if version changes,
+     *                        instead of recreating the tables.
+     * @param migrationNotRequiredFrom The collection of schema versions from which migrations
+     *                                 aren't required.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
+            @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
+            @NonNull RoomDatabase.MigrationContainer migrationContainer,
+            @Nullable List<RoomDatabase.Callback> callbacks,
+            boolean allowMainThreadQueries,
+            RoomDatabase.JournalMode journalMode,
+            boolean requireMigration,
+            @Nullable Set<Integer> migrationNotRequiredFrom) {
+        this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
+        this.context = context;
+        this.name = name;
+        this.migrationContainer = migrationContainer;
+        this.callbacks = callbacks;
+        this.allowMainThreadQueries = allowMainThreadQueries;
+        this.journalMode = journalMode;
+        this.requireMigration = requireMigration;
+        this.mMigrationNotRequiredFrom = migrationNotRequiredFrom;
+    }
+
+    /**
+     * Returns whether a migration is required from the specified version.
+     *
+     * @param version  The schema version.
+     * @return True if a valid migration is required, false otherwise.
+     */
+    public boolean isMigrationRequiredFrom(int version) {
+        // Migrations are required from this version if we generally require migrations AND EITHER
+        // there are no exceptions OR the supplied version is not one of the exceptions.
+        return requireMigration
+                && (mMigrationNotRequiredFrom == null
+                || !mMigrationNotRequiredFrom.contains(version));
+
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java b/room/runtime/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java
new file mode 100644
index 0000000..098a14b
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import androidx.annotation.RestrictTo;
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+/**
+ * Implementations of this class knows how to delete or update a particular entity.
+ * <p>
+ * This is an internal library class and all of its implementations are auto-generated.
+ *
+ * @param <T> The type parameter of the entity to be deleted
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class EntityDeletionOrUpdateAdapter<T> extends SharedSQLiteStatement {
+    /**
+     * Creates a DeletionOrUpdateAdapter that can delete or update the entity type T on the given
+     * database.
+     *
+     * @param database The database to delete / update the item in.
+     */
+    public EntityDeletionOrUpdateAdapter(RoomDatabase database) {
+        super(database);
+    }
+
+    /**
+     * Create the deletion or update query
+     *
+     * @return An SQL query that can delete or update instances of T.
+     */
+    @Override
+    protected abstract String createQuery();
+
+    /**
+     * Binds the entity into the given statement.
+     *
+     * @param statement The SQLite statement that prepared for the query returned from
+     *                  createQuery.
+     * @param entity    The entity of type T.
+     */
+    protected abstract void bind(SupportSQLiteStatement statement, T entity);
+
+    /**
+     * Deletes or updates the given entities in the database and returns the affected row count.
+     *
+     * @param entity The entity to delete or update
+     * @return The number of affected rows
+     */
+    public final int handle(T entity) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            bind(stmt, entity);
+            return stmt.executeUpdateDelete();
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Deletes or updates the given entities in the database and returns the affected row count.
+     *
+     * @param entities Entities to delete or update
+     * @return The number of affected rows
+     */
+    public final int handleMultiple(Iterable<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            int total = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                total += stmt.executeUpdateDelete();
+            }
+            return total;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Deletes or updates the given entities in the database and returns the affected row count.
+     *
+     * @param entities Entities to delete or update
+     * @return The number of affected rows
+     */
+    public final int handleMultiple(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            int total = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                total += stmt.executeUpdateDelete();
+            }
+            return total;
+        } finally {
+            release(stmt);
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/EntityInsertionAdapter.java b/room/runtime/src/main/java/androidx/room/EntityInsertionAdapter.java
new file mode 100644
index 0000000..97f3969
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/EntityInsertionAdapter.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import androidx.annotation.RestrictTo;
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Implementations of this class knows how to insert a particular entity.
+ * <p>
+ * This is an internal library class and all of its implementations are auto-generated.
+ *
+ * @param <T> The type parameter of the entity to be inserted
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class EntityInsertionAdapter<T> extends SharedSQLiteStatement {
+    /**
+     * Creates an InsertionAdapter that can insert the entity type T into the given database.
+     *
+     * @param database The database to insert into.
+     */
+    public EntityInsertionAdapter(RoomDatabase database) {
+        super(database);
+    }
+
+    /**
+     * Binds the entity into the given statement.
+     *
+     * @param statement The SQLite statement that prepared for the query returned from
+     *                  createInsertQuery.
+     * @param entity    The entity of type T.
+     */
+    protected abstract void bind(SupportSQLiteStatement statement, T entity);
+
+    /**
+     * Inserts the entity into the database.
+     *
+     * @param entity The entity to insert
+     */
+    public final void insert(T entity) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            bind(stmt, entity);
+            stmt.executeInsert();
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database.
+     *
+     * @param entities Entities to insert
+     */
+    public final void insert(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            for (T entity : entities) {
+                bind(stmt, entity);
+                stmt.executeInsert();
+            }
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database.
+     *
+     * @param entities Entities to insert
+     */
+    public final void insert(Iterable<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            for (T entity : entities) {
+                bind(stmt, entity);
+                stmt.executeInsert();
+            }
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entity into the database and returns the row id.
+     *
+     * @param entity The entity to insert
+     * @return The SQLite row id
+     */
+    public final long insertAndReturnId(T entity) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            bind(stmt, entity);
+            return stmt.executeInsert();
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final long[] insertAndReturnIdsArray(Collection<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final long[] result = new long[entities.size()];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final long[] insertAndReturnIdsArray(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final long[] result = new long[entities.length];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final Long[] insertAndReturnIdsArrayBox(Collection<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final Long[] result = new Long[entities.size()];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final Long[] insertAndReturnIdsArrayBox(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final Long[] result = new Long[entities.length];
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result[index] = stmt.executeInsert();
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final List<Long> insertAndReturnIdsList(T[] entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final List<Long> result = new ArrayList<>(entities.length);
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result.add(index, stmt.executeInsert());
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+
+    /**
+     * Inserts the given entities into the database and returns the row ids.
+     *
+     * @param entities Entities to insert
+     * @return The SQLite row ids
+     */
+    public final List<Long> insertAndReturnIdsList(Collection<T> entities) {
+        final SupportSQLiteStatement stmt = acquire();
+        try {
+            final List<Long> result = new ArrayList<>(entities.size());
+            int index = 0;
+            for (T entity : entities) {
+                bind(stmt, entity);
+                result.add(index, stmt.executeInsert());
+                index++;
+            }
+            return result;
+        } finally {
+            release(stmt);
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
new file mode 100644
index 0000000..801f5a3
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
@@ -0,0 +1,692 @@
+/*
+ * 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 androidx.room;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.arch.core.internal.SafeIterableMap;
+import androidx.collection.ArrayMap;
+import androidx.collection.ArraySet;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * InvalidationTracker keeps a list of tables modified by queries and notifies its callbacks about
+ * these tables.
+ */
+// We create an in memory table with (version, table_id) where version is an auto-increment primary
+// key and a table_id (hardcoded int from initialization).
+// ObservedTableTracker tracks list of tables we should be watching (e.g. adding triggers for).
+// Before each beginTransaction, RoomDatabase invokes InvalidationTracker to sync trigger states.
+// After each endTransaction, RoomDatabase invokes InvalidationTracker to refresh invalidated
+// tables.
+// Each update on one of the observed tables triggers an insertion into this table, hence a
+// new version.
+// Unfortunately, we cannot override the previous row because sqlite uses the conflict resolution
+// of the outer query (the thing that triggered us) so we do a cleanup as we sync instead of letting
+// SQLite override the rows.
+// https://sqlite.org/lang_createtrigger.html:  An ON CONFLICT clause may be specified as part of an
+// UPDATE or INSERT action within the body of the trigger. However if an ON CONFLICT clause is
+// specified as part of the statement causing the trigger to fire, then conflict handling policy of
+// the outer statement is used instead.
+public class InvalidationTracker {
+
+    private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"};
+
+    private static final String UPDATE_TABLE_NAME = "room_table_modification_log";
+
+    private static final String VERSION_COLUMN_NAME = "version";
+
+    private static final String TABLE_ID_COLUMN_NAME = "table_id";
+
+    private static final String CREATE_VERSION_TABLE_SQL = "CREATE TEMP TABLE " + UPDATE_TABLE_NAME
+            + "(" + VERSION_COLUMN_NAME
+            + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+            + TABLE_ID_COLUMN_NAME
+            + " INTEGER)";
+
+    @VisibleForTesting
+    static final String CLEANUP_SQL = "DELETE FROM " + UPDATE_TABLE_NAME
+            + " WHERE " + VERSION_COLUMN_NAME + " NOT IN( SELECT MAX("
+            + VERSION_COLUMN_NAME + ") FROM " + UPDATE_TABLE_NAME
+            + " GROUP BY " + TABLE_ID_COLUMN_NAME + ")";
+
+    @VisibleForTesting
+    // We always clean before selecting so it is unlikely to have the same row twice and if we
+    // do, it is not a big deal, just more data in the cursor.
+    static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME
+            + " WHERE " + VERSION_COLUMN_NAME
+            + "  > ? ORDER BY " + VERSION_COLUMN_NAME + " ASC;";
+
+    @NonNull
+    @VisibleForTesting
+    ArrayMap<String, Integer> mTableIdLookup;
+    private String[] mTableNames;
+
+    @NonNull
+    @VisibleForTesting
+    long[] mTableVersions;
+
+    private Object[] mQueryArgs = new Object[1];
+
+    // max id in the last syc
+    private long mMaxVersion = 0;
+
+    private final RoomDatabase mDatabase;
+
+    AtomicBoolean mPendingRefresh = new AtomicBoolean(false);
+
+    private volatile boolean mInitialized = false;
+
+    private volatile SupportSQLiteStatement mCleanupStatement;
+
+    private ObservedTableTracker mObservedTableTracker;
+
+    // should be accessed with synchronization only.
+    @VisibleForTesting
+    final SafeIterableMap<Observer, ObserverWrapper> mObserverMap = new SafeIterableMap<>();
+
+    /**
+     * Used by the generated code.
+     *
+     * @hide
+     */
+    @SuppressWarnings("WeakerAccess")
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public InvalidationTracker(RoomDatabase database, String... tableNames) {
+        mDatabase = database;
+        mObservedTableTracker = new ObservedTableTracker(tableNames.length);
+        mTableIdLookup = new ArrayMap<>();
+        final int size = tableNames.length;
+        mTableNames = new String[size];
+        for (int id = 0; id < size; id++) {
+            final String tableName = tableNames[id].toLowerCase(Locale.US);
+            mTableIdLookup.put(tableName, id);
+            mTableNames[id] = tableName;
+        }
+        mTableVersions = new long[tableNames.length];
+        Arrays.fill(mTableVersions, 0);
+    }
+
+    /**
+     * Internal method to initialize table tracking.
+     * <p>
+     * You should never call this method, it is called by the generated code.
+     */
+    void internalInit(SupportSQLiteDatabase database) {
+        synchronized (this) {
+            if (mInitialized) {
+                Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/.");
+                return;
+            }
+
+            database.beginTransaction();
+            try {
+                database.execSQL("PRAGMA temp_store = MEMORY;");
+                database.execSQL("PRAGMA recursive_triggers='ON';");
+                database.execSQL(CREATE_VERSION_TABLE_SQL);
+                database.setTransactionSuccessful();
+            } finally {
+                database.endTransaction();
+            }
+            syncTriggers(database);
+            mCleanupStatement = database.compileStatement(CLEANUP_SQL);
+            mInitialized = true;
+        }
+    }
+
+    private static void appendTriggerName(StringBuilder builder, String tableName,
+            String triggerType) {
+        builder.append("`")
+                .append("room_table_modification_trigger_")
+                .append(tableName)
+                .append("_")
+                .append(triggerType)
+                .append("`");
+    }
+
+    private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
+        final String tableName = mTableNames[tableId];
+        StringBuilder stringBuilder = new StringBuilder();
+        for (String trigger : TRIGGERS) {
+            stringBuilder.setLength(0);
+            stringBuilder.append("DROP TRIGGER IF EXISTS ");
+            appendTriggerName(stringBuilder, tableName, trigger);
+            writableDb.execSQL(stringBuilder.toString());
+        }
+    }
+
+    private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
+        final String tableName = mTableNames[tableId];
+        StringBuilder stringBuilder = new StringBuilder();
+        for (String trigger : TRIGGERS) {
+            stringBuilder.setLength(0);
+            stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS ");
+            appendTriggerName(stringBuilder, tableName, trigger);
+            stringBuilder.append(" AFTER ")
+                    .append(trigger)
+                    .append(" ON `")
+                    .append(tableName)
+                    .append("` BEGIN INSERT OR REPLACE INTO ")
+                    .append(UPDATE_TABLE_NAME)
+                    .append(" VALUES(null, ")
+                    .append(tableId)
+                    .append("); END");
+            writableDb.execSQL(stringBuilder.toString());
+        }
+    }
+
+    /**
+     * Adds the given observer to the observers list and it will be notified if any table it
+     * observes changes.
+     * <p>
+     * Database changes are pulled on another thread so in some race conditions, the observer might
+     * be invoked for changes that were done before it is added.
+     * <p>
+     * If the observer already exists, this is a no-op call.
+     * <p>
+     * If one of the tables in the Observer does not exist in the database, this method throws an
+     * {@link IllegalArgumentException}.
+     *
+     * @param observer The observer which listens the database for changes.
+     */
+    @WorkerThread
+    public void addObserver(@NonNull Observer observer) {
+        final String[] tableNames = observer.mTables;
+        int[] tableIds = new int[tableNames.length];
+        final int size = tableNames.length;
+        long[] versions = new long[tableNames.length];
+
+        // TODO sync versions ?
+        for (int i = 0; i < size; i++) {
+            Integer tableId = mTableIdLookup.get(tableNames[i].toLowerCase(Locale.US));
+            if (tableId == null) {
+                throw new IllegalArgumentException("There is no table with name " + tableNames[i]);
+            }
+            tableIds[i] = tableId;
+            versions[i] = mMaxVersion;
+        }
+        ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames, versions);
+        ObserverWrapper currentObserver;
+        synchronized (mObserverMap) {
+            currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
+        }
+        if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
+            syncTriggers();
+        }
+    }
+
+    /**
+     * Adds an observer but keeps a weak reference back to it.
+     * <p>
+     * Note that you cannot remove this observer once added. It will be automatically removed
+     * when the observer is GC'ed.
+     *
+     * @param observer The observer to which InvalidationTracker will keep a weak reference.
+     * @hide
+     */
+    @SuppressWarnings("unused")
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public void addWeakObserver(Observer observer) {
+        addObserver(new WeakObserver(this, observer));
+    }
+
+    /**
+     * Removes the observer from the observers list.
+     *
+     * @param observer The observer to remove.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @WorkerThread
+    public void removeObserver(@NonNull final Observer observer) {
+        ObserverWrapper wrapper;
+        synchronized (mObserverMap) {
+            wrapper = mObserverMap.remove(observer);
+        }
+        if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
+            syncTriggers();
+        }
+    }
+
+    private boolean ensureInitialization() {
+        if (!mDatabase.isOpen()) {
+            return false;
+        }
+        if (!mInitialized) {
+            // trigger initialization
+            mDatabase.getOpenHelper().getWritableDatabase();
+        }
+        if (!mInitialized) {
+            Log.e(Room.LOG_TAG, "database is not initialized even though it is open");
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    Runnable mRefreshRunnable = new Runnable() {
+        @Override
+        public void run() {
+            final Lock closeLock = mDatabase.getCloseLock();
+            boolean hasUpdatedTable = false;
+            try {
+                closeLock.lock();
+
+                if (!ensureInitialization()) {
+                    return;
+                }
+
+                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;
+                if (mDatabase.mWriteAheadLoggingEnabled) {
+                    // This transaction has to be on the underlying DB rather than the RoomDatabase
+                    // in order to avoid a recursive loop after endTransaction.
+                    SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
+                    try {
+                        db.beginTransaction();
+                        hasUpdatedTable = checkUpdatedTable();
+                        db.setTransactionSuccessful();
+                    } finally {
+                        db.endTransaction();
+                    }
+                } else {
+                    hasUpdatedTable = checkUpdatedTable();
+                }
+            } catch (IllegalStateException | SQLiteException exception) {
+                // may happen if db is closed. just log.
+                Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
+                        exception);
+            } finally {
+                closeLock.unlock();
+            }
+            if (hasUpdatedTable) {
+                synchronized (mObserverMap) {
+                    for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
+                        entry.getValue().checkForInvalidation(mTableVersions);
+                    }
+                }
+            }
+        }
+
+        private boolean checkUpdatedTable() {
+            boolean hasUpdatedTable = false;
+            Cursor cursor = mDatabase.query(SELECT_UPDATED_TABLES_SQL, mQueryArgs);
+            //noinspection TryFinallyCanBeTryWithResources
+            try {
+                while (cursor.moveToNext()) {
+                    final long version = cursor.getLong(0);
+                    final int tableId = cursor.getInt(1);
+
+                    mTableVersions[tableId] = version;
+                    hasUpdatedTable = true;
+                    // result is ordered so we can safely do this assignment
+                    mMaxVersion = version;
+                }
+            } finally {
+                cursor.close();
+            }
+            return hasUpdatedTable;
+        }
+    };
+
+    /**
+     * Enqueues a task to refresh the list of updated tables.
+     * <p>
+     * This method is automatically called when {@link RoomDatabase#endTransaction()} is called but
+     * if you have another connection to the database or directly use {@link
+     * SupportSQLiteDatabase}, you may need to call this manually.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void refreshVersionsAsync() {
+        // TODO we should consider doing this sync instead of async.
+        if (mPendingRefresh.compareAndSet(false, true)) {
+            ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+        }
+    }
+
+    /**
+     * Check versions for tables, and run observers synchronously if tables have been updated.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @WorkerThread
+    public void refreshVersionsSync() {
+        syncTriggers();
+        mRefreshRunnable.run();
+    }
+
+    void syncTriggers(SupportSQLiteDatabase database) {
+        if (database.inTransaction()) {
+            // we won't run this inside another transaction.
+            return;
+        }
+        try {
+            // This method runs in a while loop because while changes are synced to db, another
+            // runnable may be skipped. If we cause it to skip, we need to do its work.
+            while (true) {
+                Lock closeLock = mDatabase.getCloseLock();
+                closeLock.lock();
+                try {
+                    // there is a potential race condition where another mSyncTriggers runnable
+                    // can start running right after we get the tables list to sync.
+                    final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
+                    if (tablesToSync == null) {
+                        return;
+                    }
+                    final int limit = tablesToSync.length;
+                    try {
+                        database.beginTransaction();
+                        for (int tableId = 0; tableId < limit; tableId++) {
+                            switch (tablesToSync[tableId]) {
+                                case ObservedTableTracker.ADD:
+                                    startTrackingTable(database, tableId);
+                                    break;
+                                case ObservedTableTracker.REMOVE:
+                                    stopTrackingTable(database, tableId);
+                                    break;
+                            }
+                        }
+                        database.setTransactionSuccessful();
+                    } finally {
+                        database.endTransaction();
+                    }
+                    mObservedTableTracker.onSyncCompleted();
+                } finally {
+                    closeLock.unlock();
+                }
+            }
+        } catch (IllegalStateException | SQLiteException exception) {
+            // may happen if db is closed. just log.
+            Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
+                    exception);
+        }
+    }
+
+    /**
+     * Called by RoomDatabase before each beginTransaction call.
+     * <p>
+     * It is important that pending trigger changes are applied to the database before any query
+     * runs. Otherwise, we may miss some changes.
+     * <p>
+     * This api should eventually be public.
+     */
+    void syncTriggers() {
+        if (!mDatabase.isOpen()) {
+            return;
+        }
+        syncTriggers(mDatabase.getOpenHelper().getWritableDatabase());
+    }
+
+    /**
+     * Wraps an observer and keeps the table information.
+     * <p>
+     * Internally table ids are used which may change from database to database so the table
+     * related information is kept here rather than in the Observer.
+     */
+    @SuppressWarnings("WeakerAccess")
+    static class ObserverWrapper {
+        final int[] mTableIds;
+        private final String[] mTableNames;
+        private final long[] mVersions;
+        final Observer mObserver;
+        private final Set<String> mSingleTableSet;
+
+        ObserverWrapper(Observer observer, int[] tableIds, String[] tableNames, long[] versions) {
+            mObserver = observer;
+            mTableIds = tableIds;
+            mTableNames = tableNames;
+            mVersions = versions;
+            if (tableIds.length == 1) {
+                ArraySet<String> set = new ArraySet<>();
+                set.add(mTableNames[0]);
+                mSingleTableSet = Collections.unmodifiableSet(set);
+            } else {
+                mSingleTableSet = null;
+            }
+        }
+
+        void checkForInvalidation(long[] versions) {
+            Set<String> invalidatedTables = null;
+            final int size = mTableIds.length;
+            for (int index = 0; index < size; index++) {
+                final int tableId = mTableIds[index];
+                final long newVersion = versions[tableId];
+                final long currentVersion = mVersions[index];
+                if (currentVersion < newVersion) {
+                    mVersions[index] = newVersion;
+                    if (size == 1) {
+                        // Optimization for a single-table observer
+                        invalidatedTables = mSingleTableSet;
+                    } else {
+                        if (invalidatedTables == null) {
+                            invalidatedTables = new ArraySet<>(size);
+                        }
+                        invalidatedTables.add(mTableNames[index]);
+                    }
+                }
+            }
+            if (invalidatedTables != null) {
+                mObserver.onInvalidated(invalidatedTables);
+            }
+        }
+    }
+
+    /**
+     * An observer that can listen for changes in the database.
+     */
+    public abstract static class Observer {
+        final String[] mTables;
+
+        /**
+         * Observes the given list of tables.
+         *
+         * @param firstTable The table name
+         * @param rest       More table names
+         */
+        @SuppressWarnings("unused")
+        protected Observer(@NonNull String firstTable, String... rest) {
+            mTables = Arrays.copyOf(rest, rest.length + 1);
+            mTables[rest.length] = firstTable;
+        }
+
+        /**
+         * Observes the given list of tables.
+         *
+         * @param tables The list of tables to observe for changes.
+         */
+        public Observer(@NonNull String[] tables) {
+            // copy tables in case user modifies them afterwards
+            mTables = Arrays.copyOf(tables, tables.length);
+        }
+
+        /**
+         * Called when one of the observed tables is invalidated in the database.
+         *
+         * @param tables A set of invalidated tables. This is useful when the observer targets
+         *               multiple tables and want to know which table is invalidated.
+         */
+        public abstract void onInvalidated(@NonNull Set<String> tables);
+    }
+
+
+    /**
+     * Keeps a list of tables we should observe. Invalidation tracker lazily syncs this list w/
+     * triggers in the database.
+     * <p>
+     * This class is thread safe
+     */
+    static class ObservedTableTracker {
+        static final int NO_OP = 0; // don't change trigger state for this table
+        static final int ADD = 1; // add triggers for this table
+        static final int REMOVE = 2; // remove triggers for this table
+
+        // number of observers per table
+        final long[] mTableObservers;
+        // trigger state for each table at last sync
+        // this field is updated when syncAndGet is called.
+        final boolean[] mTriggerStates;
+        // when sync is called, this field is returned. It includes actions as ADD, REMOVE, NO_OP
+        final int[] mTriggerStateChanges;
+
+        boolean mNeedsSync;
+
+        /**
+         * After we return non-null value from getTablesToSync, we expect a onSyncCompleted before
+         * returning any non-null value from getTablesToSync.
+         * This allows us to workaround any multi-threaded state syncing issues.
+         */
+        boolean mPendingSync;
+
+        ObservedTableTracker(int tableCount) {
+            mTableObservers = new long[tableCount];
+            mTriggerStates = new boolean[tableCount];
+            mTriggerStateChanges = new int[tableCount];
+            Arrays.fill(mTableObservers, 0);
+            Arrays.fill(mTriggerStates, false);
+        }
+
+        /**
+         * @return true if # of triggers is affected.
+         */
+        boolean onAdded(int... tableIds) {
+            boolean needTriggerSync = false;
+            synchronized (this) {
+                for (int tableId : tableIds) {
+                    final long prevObserverCount = mTableObservers[tableId];
+                    mTableObservers[tableId] = prevObserverCount + 1;
+                    if (prevObserverCount == 0) {
+                        mNeedsSync = true;
+                        needTriggerSync = true;
+                    }
+                }
+            }
+            return needTriggerSync;
+        }
+
+        /**
+         * @return true if # of triggers is affected.
+         */
+        boolean onRemoved(int... tableIds) {
+            boolean needTriggerSync = false;
+            synchronized (this) {
+                for (int tableId : tableIds) {
+                    final long prevObserverCount = mTableObservers[tableId];
+                    mTableObservers[tableId] = prevObserverCount - 1;
+                    if (prevObserverCount == 1) {
+                        mNeedsSync = true;
+                        needTriggerSync = true;
+                    }
+                }
+            }
+            return needTriggerSync;
+        }
+
+        /**
+         * If this returns non-null, you must call onSyncCompleted.
+         *
+         * @return int[] An int array where the index for each tableId has the action for that
+         * table.
+         */
+        @Nullable
+        int[] getTablesToSync() {
+            synchronized (this) {
+                if (!mNeedsSync || mPendingSync) {
+                    return null;
+                }
+                final int tableCount = mTableObservers.length;
+                for (int i = 0; i < tableCount; i++) {
+                    final boolean newState = mTableObservers[i] > 0;
+                    if (newState != mTriggerStates[i]) {
+                        mTriggerStateChanges[i] = newState ? ADD : REMOVE;
+                    } else {
+                        mTriggerStateChanges[i] = NO_OP;
+                    }
+                    mTriggerStates[i] = newState;
+                }
+                mPendingSync = true;
+                mNeedsSync = false;
+                return mTriggerStateChanges;
+            }
+        }
+
+        /**
+         * if getTablesToSync returned non-null, the called should call onSyncCompleted once it
+         * is done.
+         */
+        void onSyncCompleted() {
+            synchronized (this) {
+                mPendingSync = false;
+            }
+        }
+    }
+
+    /**
+     * An Observer wrapper that keeps a weak reference to the given object.
+     * <p>
+     * This class with automatically unsubscribe when the wrapped observer goes out of memory.
+     */
+    static class WeakObserver extends Observer {
+        final InvalidationTracker mTracker;
+        final WeakReference<Observer> mDelegateRef;
+
+        WeakObserver(InvalidationTracker tracker, Observer delegate) {
+            super(delegate.mTables);
+            mTracker = tracker;
+            mDelegateRef = new WeakReference<>(delegate);
+        }
+
+        @Override
+        public void onInvalidated(@NonNull Set<String> tables) {
+            final Observer observer = mDelegateRef.get();
+            if (observer == null) {
+                mTracker.removeObserver(this);
+            } else {
+                observer.onInvalidated(tables);
+            }
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/Room.java b/room/runtime/src/main/java/androidx/room/Room.java
new file mode 100644
index 0000000..15107fc
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/Room.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utility class for Room.
+ */
+@SuppressWarnings("unused")
+public class Room {
+    static final String LOG_TAG = "ROOM";
+    /**
+     * The master table where room keeps its metadata information.
+     */
+    public static final String MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME;
+    private static final String CURSOR_CONV_SUFFIX = "_CursorConverter";
+
+    /**
+     * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
+     * should keep a reference to it and re-use it.
+     *
+     * @param context The context for the database. This is usually the Application context.
+     * @param klass   The abstract class which is annotated with {@link Database} and extends
+     *                {@link RoomDatabase}.
+     * @param name    The name of the database file.
+     * @param <T>     The type of the database class.
+     * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
+     */
+    @SuppressWarnings("WeakerAccess")
+    @NonNull
+    public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
+            @NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
+        //noinspection ConstantConditions
+        if (name == null || name.trim().length() == 0) {
+            throw new IllegalArgumentException("Cannot build a database with null or empty name."
+                    + " If you are trying to create an in memory database, use Room"
+                    + ".inMemoryDatabaseBuilder");
+        }
+        return new RoomDatabase.Builder<>(context, klass, name);
+    }
+
+    /**
+     * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
+     * database disappears when the process is killed.
+     * Once a database is built, you should keep a reference to it and re-use it.
+     *
+     * @param context The context for the database. This is usually the Application context.
+     * @param klass   The abstract class which is annotated with {@link Database} and extends
+     *                {@link RoomDatabase}.
+     * @param <T>     The type of the database class.
+     * @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
+     */
+    @NonNull
+    public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
+            @NonNull Context context, @NonNull Class<T> klass) {
+        return new RoomDatabase.Builder<>(context, klass, null);
+    }
+
+    @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
+    @NonNull
+    static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
+        final String fullPackage = klass.getPackage().getName();
+        String name = klass.getCanonicalName();
+        final String postPackageName = fullPackage.isEmpty()
+                ? name
+                : (name.substring(fullPackage.length() + 1));
+        final String implName = postPackageName.replace('.', '_') + suffix;
+        //noinspection TryWithIdenticalCatches
+        try {
+
+            @SuppressWarnings("unchecked")
+            final Class<T> aClass = (Class<T>) Class.forName(
+                    fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
+            return aClass.newInstance();
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException("cannot find implementation for "
+                    + klass.getCanonicalName() + ". " + implName + " does not exist");
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Cannot access the constructor"
+                    + klass.getCanonicalName());
+        } catch (InstantiationException e) {
+            throw new RuntimeException("Failed to create an instance of "
+                    + klass.getCanonicalName());
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
new file mode 100644
index 0000000..d89cf00
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
+import androidx.collection.SparseArrayCompat;
+import androidx.core.app.ActivityManagerCompat;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SimpleSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.SupportSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteStatement;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Base class for all Room databases. All classes that are annotated with {@link Database} must
+ * extend this class.
+ * <p>
+ * RoomDatabase provides direct access to the underlying database implementation but you should
+ * prefer using {@link Dao} classes.
+ *
+ * @see Database
+ */
+//@SuppressWarnings({"unused", "WeakerAccess"})
+public abstract class RoomDatabase {
+    private static final String DB_IMPL_SUFFIX = "_Impl";
+    /**
+     * Unfortunately, we cannot read this value so we are only setting it to the SQLite default.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final int MAX_BIND_PARAMETER_CNT = 999;
+    // set by the generated open helper.
+    protected volatile SupportSQLiteDatabase mDatabase;
+    private SupportSQLiteOpenHelper mOpenHelper;
+    private final InvalidationTracker mInvalidationTracker;
+    private boolean mAllowMainThreadQueries;
+    boolean mWriteAheadLoggingEnabled;
+
+    @Nullable
+    protected List<Callback> mCallbacks;
+
+    private final ReentrantLock mCloseLock = new ReentrantLock();
+
+    /**
+     * {@link InvalidationTracker} uses this lock to prevent the database from closing while it is
+     * querying database updates.
+     *
+     * @return The lock for {@link #close()}.
+     */
+    Lock getCloseLock() {
+        return mCloseLock;
+    }
+
+    /**
+     * Creates a RoomDatabase.
+     * <p>
+     * You cannot create an instance of a database, instead, you should acquire it via
+     * {@link Room#databaseBuilder(Context, Class, String)} or
+     * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
+     */
+    public RoomDatabase() {
+        mInvalidationTracker = createInvalidationTracker();
+    }
+
+    /**
+     * Called by {@link Room} when it is initialized.
+     *
+     * @param configuration The database configuration.
+     */
+    @CallSuper
+    public void init(@NonNull DatabaseConfiguration configuration) {
+        mOpenHelper = createOpenHelper(configuration);
+        boolean wal = false;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
+            mOpenHelper.setWriteAheadLoggingEnabled(wal);
+        }
+        mCallbacks = configuration.callbacks;
+        mAllowMainThreadQueries = configuration.allowMainThreadQueries;
+        mWriteAheadLoggingEnabled = wal;
+    }
+
+    /**
+     * Returns the SQLite open helper used by this database.
+     *
+     * @return The SQLite open helper used by this database.
+     */
+    @NonNull
+    public SupportSQLiteOpenHelper getOpenHelper() {
+        return mOpenHelper;
+    }
+
+    /**
+     * Creates the open helper to access the database. Generated class already implements this
+     * method.
+     * Note that this method is called when the RoomDatabase is initialized.
+     *
+     * @param config The configuration of the Room database.
+     * @return A new SupportSQLiteOpenHelper to be used while connecting to the database.
+     */
+    @NonNull
+    protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
+
+    /**
+     * Called when the RoomDatabase is created.
+     * <p>
+     * This is already implemented by the generated code.
+     *
+     * @return Creates a new InvalidationTracker.
+     */
+    @NonNull
+    protected abstract InvalidationTracker createInvalidationTracker();
+
+    /**
+     * Deletes all rows from all the tables that are registered to this database as
+     * {@link Database#entities()}.
+     * <p>
+     * This does NOT reset the auto-increment value generated by {@link PrimaryKey#autoGenerate()}.
+     * <p>
+     * After deleting the rows, Room will set a WAL checkpoint and run VACUUM. This means that the
+     * data is completely erased. The space will be reclaimed by the system if the amount surpasses
+     * the threshold of database file size.
+     *
+     * @see <a href="https://www.sqlite.org/fileformat.html">Database File Format</a>
+     */
+    @WorkerThread
+    public abstract void clearAllTables();
+
+    /**
+     * Returns true if database connection is open and initialized.
+     *
+     * @return true if the database connection is open, false otherwise.
+     */
+    public boolean isOpen() {
+        final SupportSQLiteDatabase db = mDatabase;
+        return db != null && db.isOpen();
+    }
+
+    /**
+     * Closes the database if it is already open.
+     */
+    public void close() {
+        if (isOpen()) {
+            try {
+                mCloseLock.lock();
+                mOpenHelper.close();
+            } finally {
+                mCloseLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Asserts that we are not on the main thread.
+     *
+     * @hide
+     */
+    @SuppressWarnings("WeakerAccess")
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    // used in generated code
+    public void assertNotMainThread() {
+        if (mAllowMainThreadQueries) {
+            return;
+        }
+        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.");
+        }
+    }
+
+    // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which
+    // methods we are using and also helps unit tests to mock this class without mocking
+    // all SQLite database methods.
+
+    /**
+     * Convenience method to query the database with arguments.
+     *
+     * @param query The sql query
+     * @param args The bind arguments for the placeholders in the query
+     *
+     * @return A Cursor obtained by running the given query in the Room database.
+     */
+    public Cursor query(String query, @Nullable Object[] args) {
+        return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}.
+     *
+     * @param query The Query which includes the SQL and a bind callback for bind arguments.
+     * @return Result of the query.
+     */
+    public Cursor query(SupportSQLiteQuery query) {
+        assertNotMainThread();
+        return mOpenHelper.getWritableDatabase().query(query);
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
+     *
+     * @param sql The query to compile.
+     * @return The compiled query.
+     */
+    public SupportSQLiteStatement compileStatement(@NonNull String sql) {
+        assertNotMainThread();
+        return mOpenHelper.getWritableDatabase().compileStatement(sql);
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#beginTransaction()}.
+     */
+    public void beginTransaction() {
+        assertNotMainThread();
+        SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
+        mInvalidationTracker.syncTriggers(database);
+        database.beginTransaction();
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#endTransaction()}.
+     */
+    public void endTransaction() {
+        mOpenHelper.getWritableDatabase().endTransaction();
+        if (!inTransaction()) {
+            // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
+            // endTransaction call to do it.
+            mInvalidationTracker.refreshVersionsAsync();
+        }
+    }
+
+    /**
+     * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
+     */
+    public void setTransactionSuccessful() {
+        mOpenHelper.getWritableDatabase().setTransactionSuccessful();
+    }
+
+    /**
+     * Executes the specified {@link Runnable} in a database transaction. The transaction will be
+     * marked as successful unless an exception is thrown in the {@link Runnable}.
+     *
+     * @param body The piece of code to execute.
+     */
+    public void runInTransaction(@NonNull Runnable body) {
+        beginTransaction();
+        try {
+            body.run();
+            setTransactionSuccessful();
+        } finally {
+            endTransaction();
+        }
+    }
+
+    /**
+     * Executes the specified {@link Callable} in a database transaction. The transaction will be
+     * marked as successful unless an exception is thrown in the {@link Callable}.
+     *
+     * @param body The piece of code to execute.
+     * @param <V>  The type of the return value.
+     * @return The value returned from the {@link Callable}.
+     */
+    public <V> V runInTransaction(@NonNull Callable<V> body) {
+        beginTransaction();
+        try {
+            V result = body.call();
+            setTransactionSuccessful();
+            return result;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new RuntimeException("Exception in transaction", e);
+        } finally {
+            endTransaction();
+        }
+    }
+
+    /**
+     * Called by the generated code when database is open.
+     * <p>
+     * You should never call this method manually.
+     *
+     * @param db The database instance.
+     */
+    protected void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase db) {
+        mInvalidationTracker.internalInit(db);
+    }
+
+    /**
+     * Returns the invalidation tracker for this database.
+     * <p>
+     * You can use the invalidation tracker to get notified when certain tables in the database
+     * are modified.
+     *
+     * @return The invalidation tracker for the database.
+     */
+    @NonNull
+    public InvalidationTracker getInvalidationTracker() {
+        return mInvalidationTracker;
+    }
+
+    /**
+     * Returns true if current thread is in a transaction.
+     *
+     * @return True if there is an active transaction in current thread, false otherwise.
+     * @see SupportSQLiteDatabase#inTransaction()
+     */
+    @SuppressWarnings("WeakerAccess")
+    public boolean inTransaction() {
+        return mOpenHelper.getWritableDatabase().inTransaction();
+    }
+
+    /**
+     * Journal modes for SQLite database.
+     *
+     * @see RoomDatabase.Builder#setJournalMode(JournalMode)
+     */
+    public enum JournalMode {
+
+        /**
+         * Let Room choose the journal mode. This is the default value when no explicit value is
+         * specified.
+         * <p>
+         * The actual value will be {@link #TRUNCATE} when the device runs API Level lower than 16
+         * or it is a low-RAM device. Otherwise, {@link #WRITE_AHEAD_LOGGING} will be used.
+         */
+        AUTOMATIC,
+
+        /**
+         * Truncate journal mode.
+         */
+        TRUNCATE,
+
+        /**
+         * Write-Ahead Logging mode.
+         */
+        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+        WRITE_AHEAD_LOGGING;
+
+        /**
+         * Resolves {@link #AUTOMATIC} to either {@link #TRUNCATE} or
+         * {@link #WRITE_AHEAD_LOGGING}.
+         */
+        @SuppressLint("NewApi")
+        JournalMode resolve(Context context) {
+            if (this != AUTOMATIC) {
+                return this;
+            }
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                ActivityManager manager = (ActivityManager)
+                        context.getSystemService(Context.ACTIVITY_SERVICE);
+                if (manager != null && !ActivityManagerCompat.isLowRamDevice(manager)) {
+                    return WRITE_AHEAD_LOGGING;
+                }
+            }
+            return TRUNCATE;
+        }
+    }
+
+    /**
+     * Builder for RoomDatabase.
+     *
+     * @param <T> The type of the abstract database class.
+     */
+    public static class Builder<T extends RoomDatabase> {
+        private final Class<T> mDatabaseClass;
+        private final String mName;
+        private final Context mContext;
+        private ArrayList<Callback> mCallbacks;
+
+        private SupportSQLiteOpenHelper.Factory mFactory;
+        private boolean mAllowMainThreadQueries;
+        private JournalMode mJournalMode;
+        private boolean mRequireMigration;
+        /**
+         * Migrations, mapped by from-to pairs.
+         */
+        private final MigrationContainer mMigrationContainer;
+        private Set<Integer> mMigrationsNotRequiredFrom;
+        /**
+         * Keeps track of {@link Migration#startVersion}s and {@link Migration#endVersion}s added in
+         * {@link #addMigrations(Migration...)} for later validation that makes those versions don't
+         * match any versions passed to {@link #fallbackToDestructiveMigrationFrom(int...)}.
+         */
+        private Set<Integer> mMigrationStartAndEndVersions;
+
+        Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
+            mContext = context;
+            mDatabaseClass = klass;
+            mName = name;
+            mJournalMode = JournalMode.AUTOMATIC;
+            mRequireMigration = true;
+            mMigrationContainer = new MigrationContainer();
+        }
+
+        /**
+         * Sets the database factory. If not set, it defaults to
+         * {@link FrameworkSQLiteOpenHelperFactory}.
+         *
+         * @param factory The factory to use to access the database.
+         * @return this
+         */
+        @NonNull
+        public Builder<T> openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) {
+            mFactory = factory;
+            return this;
+        }
+
+        /**
+         * Adds a migration to the builder.
+         * <p>
+         * Each Migration has a start and end versions and Room runs these migrations to bring the
+         * database to the latest version.
+         * <p>
+         * If a migration item is missing between current version and the latest version, Room
+         * will clear the database and recreate so even if you have no changes between 2 versions,
+         * you should still provide a Migration object to the builder.
+         * <p>
+         * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
+         * going version 3 to 5 without going to version 4). If Room opens a database at version
+         * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
+         * 3 to 5 instead of 3 to 4 and 4 to 5.
+         *
+         * @param migrations The migration object that can modify the database and to the necessary
+         *                   changes.
+         * @return this
+         */
+        @NonNull
+        public Builder<T> addMigrations(@NonNull  Migration... migrations) {
+            if (mMigrationStartAndEndVersions == null) {
+                mMigrationStartAndEndVersions = new HashSet<>();
+            }
+            for (Migration migration: migrations) {
+                mMigrationStartAndEndVersions.add(migration.startVersion);
+                mMigrationStartAndEndVersions.add(migration.endVersion);
+            }
+
+            mMigrationContainer.addMigrations(migrations);
+            return this;
+        }
+
+        /**
+         * Disables the main thread query check for Room.
+         * <p>
+         * Room ensures that Database is never accessed on the main thread because it may lock the
+         * main thread and trigger an ANR. If you need to access the database from the main thread,
+         * you should always use async alternatives or manually move the call to a background
+         * thread.
+         * <p>
+         * You may want to turn this check off for testing.
+         *
+         * @return this
+         */
+        @NonNull
+        public Builder<T> allowMainThreadQueries() {
+            mAllowMainThreadQueries = true;
+            return this;
+        }
+
+        /**
+         * Sets the journal mode for this database.
+         *
+         * <p>
+         * This value is ignored if the builder is initialized with
+         * {@link Room#inMemoryDatabaseBuilder(Context, Class)}.
+         * <p>
+         * The journal mode should be consistent across multiple instances of
+         * {@link RoomDatabase} for a single SQLite database file.
+         * <p>
+         * The default value is {@link JournalMode#AUTOMATIC}.
+         *
+         * @param journalMode The journal mode.
+         * @return this
+         */
+        @NonNull
+        public Builder<T> setJournalMode(@NonNull JournalMode journalMode) {
+            mJournalMode = journalMode;
+            return this;
+        }
+
+        /**
+         * 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>
+         * If it cannot find the set of {@link Migration}s that will bring the database to the
+         * current version, it will throw an {@link IllegalStateException}.
+         * <p>
+         * You can call this method to change this behavior to re-create the database instead of
+         * crashing.
+         * <p>
+         * Note that this will delete all of the data in the database tables managed by Room.
+         *
+         * @return this
+         */
+        @NonNull
+        public Builder<T> fallbackToDestructiveMigration() {
+            mRequireMigration = false;
+            return this;
+        }
+
+        /**
+         * Informs Room that it is allowed to destructively recreate database tables from specific
+         * starting schema versions.
+         * <p>
+         * This functionality is the same as that provided by
+         * {@link #fallbackToDestructiveMigration()}, except that this method allows the
+         * specification of a set of schema versions for which destructive recreation is allowed.
+         * <p>
+         * Using this method is preferable to {@link #fallbackToDestructiveMigration()} if you want
+         * to allow destructive migrations from some schema versions while still taking advantage
+         * of exceptions being thrown due to unintentionally missing migrations.
+         * <p>
+         * Note: No versions passed to this method may also exist as either starting or ending
+         * versions in the {@link Migration}s provided to {@link #addMigrations(Migration...)}. If a
+         * version passed to this method is found as a starting or ending version in a Migration, an
+         * exception will be thrown.
+         *
+         * @param startVersions The set of schema versions from which Room should use a destructive
+         *                      migration.
+         * @return this
+         */
+        @NonNull
+        public Builder<T> fallbackToDestructiveMigrationFrom(int... startVersions) {
+            if (mMigrationsNotRequiredFrom == null) {
+                mMigrationsNotRequiredFrom = new HashSet<>(startVersions.length);
+            }
+            for (int startVersion : startVersions) {
+                mMigrationsNotRequiredFrom.add(startVersion);
+            }
+            return this;
+        }
+
+        /**
+         * Adds a {@link Callback} to this database.
+         *
+         * @param callback The callback.
+         * @return this
+         */
+        @NonNull
+        public Builder<T> addCallback(@NonNull Callback callback) {
+            if (mCallbacks == null) {
+                mCallbacks = new ArrayList<>();
+            }
+            mCallbacks.add(callback);
+            return this;
+        }
+
+        /**
+         * Creates the databases and initializes it.
+         * <p>
+         * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
+         * triggers.
+         *
+         * @return A new database instance.
+         */
+        @NonNull
+        public T build() {
+            //noinspection ConstantConditions
+            if (mContext == null) {
+                throw new IllegalArgumentException("Cannot provide null context for the database.");
+            }
+            //noinspection ConstantConditions
+            if (mDatabaseClass == null) {
+                throw new IllegalArgumentException("Must provide an abstract class that"
+                        + " extends RoomDatabase");
+            }
+
+            if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
+                for (Integer version : mMigrationStartAndEndVersions) {
+                    if (mMigrationsNotRequiredFrom.contains(version)) {
+                        throw new IllegalArgumentException(
+                                "Inconsistency detected. A Migration was supplied to "
+                                        + "addMigration(Migration... migrations) that has a start "
+                                        + "or end version equal to a start version supplied to "
+                                        + "fallbackToDestructiveMigrationFrom(int... "
+                                        + "startVersions). Start version: "
+                                        + version);
+                    }
+                }
+            }
+
+            if (mFactory == null) {
+                mFactory = new FrameworkSQLiteOpenHelperFactory();
+            }
+            DatabaseConfiguration configuration =
+                    new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
+                            mCallbacks, mAllowMainThreadQueries,
+                            mJournalMode.resolve(mContext),
+                            mRequireMigration, mMigrationsNotRequiredFrom);
+            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
+            db.init(configuration);
+            return db;
+        }
+    }
+
+    /**
+     * A container to hold migrations. It also allows querying its contents to find migrations
+     * between two versions.
+     */
+    public static class MigrationContainer {
+        private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
+                new SparseArrayCompat<>();
+
+        /**
+         * Adds the given migrations to the list of available migrations. If 2 migrations have the
+         * same start-end versions, the latter migration overrides the previous one.
+         *
+         * @param migrations List of available migrations.
+         */
+        public void addMigrations(@NonNull Migration... migrations) {
+            for (Migration migration : migrations) {
+                addMigration(migration);
+            }
+        }
+
+        private void addMigration(Migration migration) {
+            final int start = migration.startVersion;
+            final int end = migration.endVersion;
+            SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
+            if (targetMap == null) {
+                targetMap = new SparseArrayCompat<>();
+                mMigrations.put(start, targetMap);
+            }
+            Migration existing = targetMap.get(end);
+            if (existing != null) {
+                Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
+            }
+            targetMap.append(end, migration);
+        }
+
+        /**
+         * Finds the list of migrations that should be run to move from {@code start} version to
+         * {@code end} version.
+         *
+         * @param start The current database version
+         * @param end   The target database version
+         * @return An ordered list of {@link Migration} objects that should be run to migrate
+         * between the given versions. If a migration path cannot be found, returns {@code null}.
+         */
+        @SuppressWarnings("WeakerAccess")
+        @Nullable
+        public List<Migration> findMigrationPath(int start, int end) {
+            if (start == end) {
+                return Collections.emptyList();
+            }
+            boolean migrateUp = end > start;
+            List<Migration> result = new ArrayList<>();
+            return findUpMigrationPath(result, migrateUp, start, end);
+        }
+
+        private List<Migration> findUpMigrationPath(List<Migration> result, boolean upgrade,
+                int start, int end) {
+            final int searchDirection = upgrade ? -1 : 1;
+            while (upgrade ? start < end : start > end) {
+                SparseArrayCompat<Migration> targetNodes = mMigrations.get(start);
+                if (targetNodes == null) {
+                    return null;
+                }
+                // keys are ordered so we can start searching from one end of them.
+                final int size = targetNodes.size();
+                final int firstIndex;
+                final int lastIndex;
+
+                if (upgrade) {
+                    firstIndex = size - 1;
+                    lastIndex = -1;
+                } else {
+                    firstIndex = 0;
+                    lastIndex = size;
+                }
+                boolean found = false;
+                for (int i = firstIndex; i != lastIndex; i += searchDirection) {
+                    final int targetVersion = targetNodes.keyAt(i);
+                    final boolean shouldAddToPath;
+                    if (upgrade) {
+                        shouldAddToPath = targetVersion <= end && targetVersion > start;
+                    } else {
+                        shouldAddToPath = targetVersion >= end && targetVersion < start;
+                    }
+                    if (shouldAddToPath) {
+                        result.add(targetNodes.valueAt(i));
+                        start = targetVersion;
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    return null;
+                }
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Callback for {@link RoomDatabase}.
+     */
+    public abstract static class Callback {
+
+        /**
+         * Called when the database is created for the first time. This is called after all the
+         * tables are created.
+         *
+         * @param db The database.
+         */
+        public void onCreate(@NonNull SupportSQLiteDatabase db) {
+        }
+
+        /**
+         * Called when the database has been opened.
+         *
+         * @param db The database.
+         */
+        public void onOpen(@NonNull SupportSQLiteDatabase db) {
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/RoomOpenHelper.java b/room/runtime/src/main/java/androidx/room/RoomOpenHelper.java
new file mode 100644
index 0000000..d42a95f
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/RoomOpenHelper.java
@@ -0,0 +1,184 @@
+/*
+ * 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 androidx.room;
+
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SimpleSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+import java.util.List;
+
+/**
+ * An open helper that holds a reference to the configuration until the database is opened.
+ *
+ * @hide
+ */
+@SuppressWarnings("unused")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
+    @Nullable
+    private DatabaseConfiguration mConfiguration;
+    @NonNull
+    private final Delegate mDelegate;
+    @NonNull
+    private final String mIdentityHash;
+    /**
+     * Room v1 had a bug where the hash was not consistent if fields are reordered.
+     * The new has fixes it but we still need to accept the legacy hash.
+     */
+    @NonNull // b/64290754
+    private final String mLegacyHash;
+
+    public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
+            @NonNull String identityHash, @NonNull String legacyHash) {
+        super(delegate.version);
+        mConfiguration = configuration;
+        mDelegate = delegate;
+        mIdentityHash = identityHash;
+        mLegacyHash = legacyHash;
+    }
+
+    @Override
+    public void onConfigure(SupportSQLiteDatabase db) {
+        super.onConfigure(db);
+    }
+
+    @Override
+    public void onCreate(SupportSQLiteDatabase db) {
+        updateIdentity(db);
+        mDelegate.createAllTables(db);
+        mDelegate.onCreate(db);
+    }
+
+    @Override
+    public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
+        boolean migrated = false;
+        if (mConfiguration != null) {
+            List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
+                    oldVersion, newVersion);
+            if (migrations != null) {
+                for (Migration migration : migrations) {
+                    migration.migrate(db);
+                }
+                mDelegate.validateMigration(db);
+                updateIdentity(db);
+                migrated = true;
+            }
+        }
+        if (!migrated) {
+            if (mConfiguration != null && !mConfiguration.isMigrationRequiredFrom(oldVersion)) {
+                mDelegate.dropAllTables(db);
+                mDelegate.createAllTables(db);
+            } else {
+                throw new IllegalStateException("A migration from " + oldVersion + " to "
+                        + newVersion + " was required but not found. Please provide the "
+                        + "necessary Migration path via "
+                        + "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
+                        + "destructive migrations via one of the "
+                        + "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
+            }
+        }
+    }
+
+    @Override
+    public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
+        onUpgrade(db, oldVersion, newVersion);
+    }
+
+    @Override
+    public void onOpen(SupportSQLiteDatabase db) {
+        super.onOpen(db);
+        checkIdentity(db);
+        mDelegate.onOpen(db);
+        // there might be too many configurations etc, just clear it.
+        mConfiguration = null;
+    }
+
+    private void checkIdentity(SupportSQLiteDatabase db) {
+        String identityHash = "";
+        if (hasRoomMasterTable(db)) {
+            Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
+            //noinspection TryFinallyCanBeTryWithResources
+            try {
+                if (cursor.moveToFirst()) {
+                    identityHash = cursor.getString(0);
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+        if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) {
+            throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
+                    + " you've changed schema but forgot to update the version number. You can"
+                    + " simply fix this by increasing the version number.");
+        }
+    }
+
+    private void updateIdentity(SupportSQLiteDatabase db) {
+        createMasterTableIfNotExists(db);
+        db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
+    }
+
+    private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
+        db.execSQL(RoomMasterTable.CREATE_QUERY);
+    }
+
+    private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) {
+        Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='"
+                + RoomMasterTable.TABLE_NAME + "'");
+        //noinspection TryFinallyCanBeTryWithResources
+        try {
+            return cursor.moveToFirst() && cursor.getInt(0) != 0;
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @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);
+
+        protected abstract void onOpen(SupportSQLiteDatabase database);
+
+        protected abstract void onCreate(SupportSQLiteDatabase database);
+
+        /**
+         * Called after a migration run to validate database integrity.
+         *
+         * @param db The SQLite database.
+         */
+        protected abstract void validateMigration(SupportSQLiteDatabase db);
+    }
+
+}
diff --git a/room/runtime/src/main/java/androidx/room/RoomSQLiteQuery.java b/room/runtime/src/main/java/androidx/room/RoomSQLiteQuery.java
new file mode 100644
index 0000000..08db4a4
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/RoomSQLiteQuery.java
@@ -0,0 +1,299 @@
+/*
+ * 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 androidx.room;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.sqlite.db.SupportSQLiteProgram;
+import androidx.sqlite.db.SupportSQLiteQuery;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * This class is used as an intermediate place to keep binding arguments so that we can run
+ * Cursor queries with correct types rather than passing everything as a string.
+ * <p>
+ * Because it is relatively a big object, they are pooled and must be released after each use.
+ *
+ * @hide
+ */
+@SuppressWarnings("unused")
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram {
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    // Maximum number of queries we'll keep cached.
+    static final int POOL_LIMIT = 15;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    // Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always
+    // clear the bigger queries (# of arguments).
+    static final int DESIRED_POOL_SIZE = 10;
+    private volatile String mQuery;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final long[] mLongBindings;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final double[] mDoubleBindings;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final String[] mStringBindings;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final byte[][] mBlobBindings;
+
+    @Binding
+    private final int[] mBindingTypes;
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    final int mCapacity;
+    // number of arguments in the query
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    int mArgCount;
+
+
+    @SuppressWarnings("WeakerAccess")
+    @VisibleForTesting
+    static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>();
+
+    /**
+     * Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery.
+     *
+     * @param supportSQLiteQuery The query to copy from
+     * @return A new query copied from the provided one.
+     */
+    public static RoomSQLiteQuery copyFrom(SupportSQLiteQuery supportSQLiteQuery) {
+        final RoomSQLiteQuery query = RoomSQLiteQuery.acquire(
+                supportSQLiteQuery.getSql(),
+                supportSQLiteQuery.getArgCount());
+        supportSQLiteQuery.bindTo(new SupportSQLiteProgram() {
+            @Override
+            public void bindNull(int index) {
+                query.bindNull(index);
+            }
+
+            @Override
+            public void bindLong(int index, long value) {
+                query.bindLong(index, value);
+            }
+
+            @Override
+            public void bindDouble(int index, double value) {
+                query.bindDouble(index, value);
+            }
+
+            @Override
+            public void bindString(int index, String value) {
+                query.bindString(index, value);
+            }
+
+            @Override
+            public void bindBlob(int index, byte[] value) {
+                query.bindBlob(index, value);
+            }
+
+            @Override
+            public void clearBindings() {
+                query.clearBindings();
+            }
+
+            @Override
+            public void close() {
+                // ignored.
+            }
+        });
+        return query;
+    }
+
+    /**
+     * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
+     * given query.
+     *
+     * @param query         The query to prepare
+     * @param argumentCount The number of query arguments
+     * @return A RoomSQLiteQuery that holds the given query and has space for the given number of
+     * arguments.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static RoomSQLiteQuery acquire(String query, int argumentCount) {
+        synchronized (sQueryPool) {
+            final Map.Entry<Integer, RoomSQLiteQuery> entry =
+                    sQueryPool.ceilingEntry(argumentCount);
+            if (entry != null) {
+                sQueryPool.remove(entry.getKey());
+                final RoomSQLiteQuery sqliteQuery = entry.getValue();
+                sqliteQuery.init(query, argumentCount);
+                return sqliteQuery;
+            }
+        }
+        RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount);
+        sqLiteQuery.init(query, argumentCount);
+        return sqLiteQuery;
+    }
+
+    private RoomSQLiteQuery(int capacity) {
+        mCapacity = capacity;
+        // because, 1 based indices... we don't want to offsets everything with 1 all the time.
+        int limit = capacity + 1;
+        //noinspection WrongConstant
+        mBindingTypes = new int[limit];
+        mLongBindings = new long[limit];
+        mDoubleBindings = new double[limit];
+        mStringBindings = new String[limit];
+        mBlobBindings = new byte[limit][];
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    void init(String query, int argCount) {
+        mQuery = query;
+        mArgCount = argCount;
+    }
+
+    /**
+     * Releases the query back to the pool.
+     * <p>
+     * After released, the statement might be returned when {@link #acquire(String, int)} is called
+     * so you should never re-use it after releasing.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void release() {
+        synchronized (sQueryPool) {
+            sQueryPool.put(mCapacity, this);
+            prunePoolLocked();
+        }
+    }
+
+    private static void prunePoolLocked() {
+        if (sQueryPool.size() > POOL_LIMIT) {
+            int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE;
+            final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator();
+            while (toBeRemoved-- > 0) {
+                iterator.next();
+                iterator.remove();
+            }
+        }
+    }
+
+    @Override
+    public String getSql() {
+        return mQuery;
+    }
+
+    @Override
+    public int getArgCount() {
+        return mArgCount;
+    }
+
+    @Override
+    public void bindTo(SupportSQLiteProgram program) {
+        for (int index = 1; index <= mArgCount; index++) {
+            switch (mBindingTypes[index]) {
+                case NULL:
+                    program.bindNull(index);
+                    break;
+                case LONG:
+                    program.bindLong(index, mLongBindings[index]);
+                    break;
+                case DOUBLE:
+                    program.bindDouble(index, mDoubleBindings[index]);
+                    break;
+                case STRING:
+                    program.bindString(index, mStringBindings[index]);
+                    break;
+                case BLOB:
+                    program.bindBlob(index, mBlobBindings[index]);
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void bindNull(int index) {
+        mBindingTypes[index] = NULL;
+    }
+
+    @Override
+    public void bindLong(int index, long value) {
+        mBindingTypes[index] = LONG;
+        mLongBindings[index] = value;
+    }
+
+    @Override
+    public void bindDouble(int index, double value) {
+        mBindingTypes[index] = DOUBLE;
+        mDoubleBindings[index] = value;
+    }
+
+    @Override
+    public void bindString(int index, String value) {
+        mBindingTypes[index] = STRING;
+        mStringBindings[index] = value;
+    }
+
+    @Override
+    public void bindBlob(int index, byte[] value) {
+        mBindingTypes[index] = BLOB;
+        mBlobBindings[index] = value;
+    }
+
+    @Override
+    public void close() {
+        // no-op. not calling release because it is internal API.
+    }
+
+    /**
+     * Copies arguments from another RoomSQLiteQuery into this query.
+     *
+     * @param other The other query, which holds the arguments to be copied.
+     */
+    public void copyArgumentsFrom(RoomSQLiteQuery other) {
+        int argCount = other.getArgCount() + 1; // +1 for the binding offsets
+        System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount);
+        System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount);
+        System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount);
+        System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount);
+        System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount);
+    }
+
+    @Override
+    public void clearBindings() {
+        Arrays.fill(mBindingTypes, NULL);
+        Arrays.fill(mStringBindings, null);
+        Arrays.fill(mBlobBindings, null);
+        mQuery = null;
+        // no need to clear others
+    }
+
+    private static final int NULL = 1;
+    private static final int LONG = 2;
+    private static final int DOUBLE = 3;
+    private static final int STRING = 4;
+    private static final int BLOB = 5;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NULL, LONG, DOUBLE, STRING, BLOB})
+    @interface Binding {
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/SharedSQLiteStatement.java b/room/runtime/src/main/java/androidx/room/SharedSQLiteStatement.java
new file mode 100644
index 0000000..57d3340
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/SharedSQLiteStatement.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room;
+
+import androidx.annotation.RestrictTo;
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Represents a prepared SQLite state that can be re-used multiple times.
+ * <p>
+ * This class is used by generated code. After it is used, {@code release} must be called so that
+ * it can be used by other threads.
+ * <p>
+ * To avoid re-entry even within the same thread, this class allows only 1 time access to the shared
+ * statement until it is released.
+ *
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class SharedSQLiteStatement {
+    private final AtomicBoolean mLock = new AtomicBoolean(false);
+
+    private final RoomDatabase mDatabase;
+    private volatile SupportSQLiteStatement mStmt;
+
+    /**
+     * Creates an SQLite prepared statement that can be re-used across threads. If it is in use,
+     * it automatically creates a new one.
+     *
+     * @param database The database to create the statement in.
+     */
+    public SharedSQLiteStatement(RoomDatabase database) {
+        mDatabase = database;
+    }
+
+    /**
+     * Create the query.
+     *
+     * @return The SQL query to prepare.
+     */
+    protected abstract String createQuery();
+
+    protected void assertNotMainThread() {
+        mDatabase.assertNotMainThread();
+    }
+
+    private SupportSQLiteStatement createNewStatement() {
+        String query = createQuery();
+        return mDatabase.compileStatement(query);
+    }
+
+    private SupportSQLiteStatement getStmt(boolean canUseCached) {
+        final SupportSQLiteStatement stmt;
+        if (canUseCached) {
+            if (mStmt == null) {
+                mStmt = createNewStatement();
+            }
+            stmt = mStmt;
+        } else {
+            // it is in use, create a one off statement
+            stmt = createNewStatement();
+        }
+        return stmt;
+    }
+
+    /**
+     * Call this to get the statement. Must call {@link #release(SupportSQLiteStatement)} once done.
+     */
+    public SupportSQLiteStatement acquire() {
+        assertNotMainThread();
+        return getStmt(mLock.compareAndSet(false, true));
+    }
+
+    /**
+     * Must call this when statement will not be used anymore.
+     *
+     * @param statement The statement that was returned from acquire.
+     */
+    public void release(SupportSQLiteStatement statement) {
+        if (statement == mStmt) {
+            mLock.set(false);
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/migration/Migration.java b/room/runtime/src/main/java/androidx/room/migration/Migration.java
new file mode 100644
index 0000000..4aa7a7e
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/migration/Migration.java
@@ -0,0 +1,63 @@
+/*
+ * 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 androidx.room.migration;
+
+import androidx.annotation.NonNull;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Base class for a database migration.
+ * <p>
+ * Each migration can move between 2 versions that are defined by {@link #startVersion} and
+ * {@link #endVersion}.
+ * <p>
+ * A migration can handle more than 1 version (e.g. if you have a faster path to choose when
+ * going version 3 to 5 without going to version 4). If Room opens a database at version
+ * 3 and latest version is &gt;= 5, Room will use the migration object that can migrate from
+ * 3 to 5 instead of 3 to 4 and 4 to 5.
+ * <p>
+ * If there are not enough migrations provided to move from the current version to the latest
+ * version, Room will clear the database and recreate so even if you have no changes between 2
+ * versions, you should still provide a Migration object to the builder.
+ */
+public abstract class Migration {
+    public final int startVersion;
+    public final int endVersion;
+
+    /**
+     * Creates a new migration between {@code startVersion} and {@code endVersion}.
+     *
+     * @param startVersion The start version of the database.
+     * @param endVersion The end version of the database after this migration is applied.
+     */
+    public Migration(int startVersion, int endVersion) {
+        this.startVersion = startVersion;
+        this.endVersion = endVersion;
+    }
+
+    /**
+     * Should run the necessary migrations.
+     * <p>
+     * This class cannot access any generated Dao in this method.
+     * <p>
+     * This method is already called inside a transaction and that transaction might actually be a
+     * composite transaction of all necessary {@code Migration}s.
+     *
+     * @param database The database instance
+     */
+    public abstract void migrate(@NonNull SupportSQLiteDatabase database);
+}
diff --git a/room/runtime/src/main/java/androidx/room/package-info.java b/room/runtime/src/main/java/androidx/room/package-info.java
new file mode 100644
index 0000000..b691f0a
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/package-info.java
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+/**
+ * Room is a Database Object Mapping library that makes it easy to access database on Android
+ * applications.
+ * <p>
+ * Rather than hiding the detail of SQLite, Room tries to embrace them by providing convenient APIs
+ * to query the database and also verify such queries at compile time. This allows you to access
+ * the full power of SQLite while having the type safety provided by Java SQL query builders.
+ * <p>
+ * There are 3 major components in Room.
+ * <ul>
+ *     <li>{@link androidx.room.Database Database}: This annotation marks a
+ *     class as a database. It should be an abstract class that extends
+ *     {@link androidx.room.RoomDatabase RoomDatabase}. At runtime, you can acquire
+ *     an instance of it via {@link androidx.room.Room#databaseBuilder(
+ *     android.content.Context,java.lang.Class, java.lang.String) Room.databaseBuilder} or
+ *     {@link androidx.room.Room#inMemoryDatabaseBuilder(android.content.Context,
+ *     java.lang.Class) Room.inMemoryDatabaseBuilder}.
+ *     <p>
+ *         This class defines the list of entities and data access objects in the database. It is
+ *         also the main access point for the underlying connection.
+ *     </li>
+ *     <li>{@link androidx.room.Entity Entity}: This annotation marks a class as a
+ *     database row. For each {@link androidx.room.Entity Entity}, a database table
+ *     is created to hold the items. The Entity class must be referenced in the
+ *     {@link androidx.room.Database#entities() Database#entities} array. Each field
+ *     of the Entity (and its super class) is persisted in the database unless it is denoted
+ *     otherwise (see {@link androidx.room.Entity Entity} docs for details).
+ *     </li>
+ *     <li>{@link androidx.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
+ *     responsible for defining the methods that access the database. The class that is annotated
+ *     with {@link androidx.room.Database Database} must have an abstract method
+ *     that has 0 arguments and returns the class that is annotated with Dao. While generating the
+ *     code at compile time, Room will generate an implementation of this class.
+ *     <pre>
+ *     Using Dao classes for database access rather than query builders or direct queries allows you
+ *     to keep a separation between different components and easily mock the database access while
+ *     testing your application.
+ *     </li>
+ * </ul>
+ * Below is a sample of a simple database.
+ * <pre>
+ * // File: User.java
+ * {@literal @}Entity
+ * public class User {
+ *   {@literal @}PrimaryKey
+ *   private int uid;
+ *   private String name;
+ *   {@literal @}ColumnInfo(name = "last_name")
+ *   private String lastName;
+ *   // getters and setters are ignored for brevity but they are required for Room to work.
+ * }
+ * // File: UserDao.java
+ * {@literal @}Dao
+ * public interface UserDao {
+ *   {@literal @}Query("SELECT * FROM user")
+ *   List&lt;User&gt; loadAll();
+ *   {@literal @}Query("SELECT * FROM user WHERE uid IN (:userIds)")
+ *   List&lt;User&gt; loadAllByUserId(int... userIds);
+ *   {@literal @}Query("SELECT * FROM user where name LIKE :first AND last_name LIKE :last LIMIT 1")
+ *   User loadOneByNameAndLastName(String first, String last);
+ *   {@literal @}Insert
+ *   void insertAll(User... users);
+ *   {@literal @}Delete
+ *   void delete(User user);
+ * }
+ * // File: AppDatabase.java
+ * {@literal @}Database(entities = {User.java})
+ * public abstract class AppDatabase extends RoomDatabase {
+ *   public abstract UserDao userDao();
+ * }
+ * </pre>
+ * You can create an instance of {@code AppDatabase} as follows:
+ * <pre>
+ * AppDatabase db = Room
+ *     .databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name")
+ *     .build();
+ * </pre>
+ * Since Room verifies your queries at compile time, it also detects information about which tables
+ * are accessed by the query or what columns are present in the response.
+ * <p>
+ * You can observe a particular table for changes using the
+ * {@link androidx.room.InvalidationTracker InvalidationTracker} class which you can
+ * acquire via {@link androidx.room.RoomDatabase#getInvalidationTracker()
+ * RoomDatabase.getInvalidationTracker}.
+ * <p>
+ * For convenience, Room allows you to return {@link androidx.lifecycle.LiveData
+ * LiveData} from {@link androidx.room.Query Query} methods. It will automatically
+ * observe the related tables as long as the {@code LiveData} has active observers.
+ * <pre>
+ * // This live data will automatically dispatch changes as the database changes.
+ * {@literal @}Query("SELECT * FROM user ORDER BY name LIMIT 5")
+ * LiveData&lt;User&gt; loadFirstFiveUsers();
+ * </pre>
+ * <p>
+ * You can also return arbitrary Java objects from your query results as long as the fields in the
+ * object match the list of columns in the query response. This makes it very easy to write
+ * applications that drive the UI from persistent storage.
+ * <pre>
+ * class IdAndFullName {
+ *     public int uid;
+ *     {@literal @}ColumnInfo(name = "full_name")
+ *     public String fullName;
+ * }
+ * // DAO
+ * {@literal @}Query("SELECT uid, name || lastName as full_name FROM user")
+ * public IdAndFullName[] loadFullNames();
+ * </pre>
+ * If there is a mismatch between the query result and the POJO, Room will print a warning during
+ * compilation.
+ * <p>
+ * Please see the documentation of individual classes for details.
+ */
+package androidx.room;
diff --git a/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java b/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
new file mode 100644
index 0000000..282389c
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
@@ -0,0 +1,176 @@
+/*
+ * 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 androidx.room.paging;
+
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.paging.PositionalDataSource;
+import androidx.room.InvalidationTracker;
+import androidx.room.RoomDatabase;
+import androidx.room.RoomSQLiteQuery;
+import androidx.sqlite.db.SupportSQLiteQuery;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A simple data source implementation that uses Limit & Offset to page the query.
+ * <p>
+ * This is NOT the most efficient way to do paging on SQLite. It is
+ * <a href="http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor">recommended</a> to use an indexed
+ * ORDER BY statement but that requires a more complex API. This solution is technically equal to
+ * receiving a {@link Cursor} from a large query but avoids the need to manually manage it, and
+ * never returns inconsistent data if it is invalidated.
+ *
+ * @param <T> Data type returned by the data source.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class LimitOffsetDataSource<T> extends PositionalDataSource<T> {
+    private final RoomSQLiteQuery mSourceQuery;
+    private final String mCountQuery;
+    private final String mLimitOffsetQuery;
+    private final RoomDatabase mDb;
+    @SuppressWarnings("FieldCanBeLocal")
+    private final InvalidationTracker.Observer mObserver;
+    private final boolean mInTransaction;
+
+    protected LimitOffsetDataSource(RoomDatabase db, SupportSQLiteQuery query,
+            boolean inTransaction, String... tables) {
+        this(db, RoomSQLiteQuery.copyFrom(query), inTransaction, tables);
+    }
+
+    protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
+            boolean inTransaction, String... tables) {
+        mDb = db;
+        mSourceQuery = query;
+        mInTransaction = inTransaction;
+        mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
+        mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
+        mObserver = new InvalidationTracker.Observer(tables) {
+            @Override
+            public void onInvalidated(@NonNull Set<String> tables) {
+                invalidate();
+            }
+        };
+        db.getInvalidationTracker().addWeakObserver(mObserver);
+    }
+
+    /**
+     * Count number of rows query can return
+     */
+    @SuppressWarnings("WeakerAccess")
+    public int countItems() {
+        final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mCountQuery,
+                mSourceQuery.getArgCount());
+        sqLiteQuery.copyArgumentsFrom(mSourceQuery);
+        Cursor cursor = mDb.query(sqLiteQuery);
+        try {
+            if (cursor.moveToFirst()) {
+                return cursor.getInt(0);
+            }
+            return 0;
+        } finally {
+            cursor.close();
+            sqLiteQuery.release();
+        }
+    }
+
+    @Override
+    public boolean isInvalid() {
+        mDb.getInvalidationTracker().refreshVersionsSync();
+        return super.isInvalid();
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    protected abstract List<T> convertRows(Cursor cursor);
+
+    @Override
+    public void loadInitial(@NonNull LoadInitialParams params,
+            @NonNull LoadInitialCallback<T> callback) {
+        int totalCount = countItems();
+        if (totalCount == 0) {
+            callback.onResult(Collections.<T>emptyList(), 0, 0);
+            return;
+        }
+
+        // bound the size requested, based on known count
+        final int firstLoadPosition = computeInitialLoadPosition(params, totalCount);
+        final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);
+
+        List<T> list = loadRange(firstLoadPosition, firstLoadSize);
+        if (list != null && list.size() == firstLoadSize) {
+            callback.onResult(list, firstLoadPosition, totalCount);
+        } else {
+            // null list, or size doesn't match request - DB modified between count and load
+            invalidate();
+        }
+    }
+
+    @Override
+    public void loadRange(@NonNull LoadRangeParams params,
+            @NonNull LoadRangeCallback<T> callback) {
+        List<T> list = loadRange(params.startPosition, params.loadSize);
+        if (list != null) {
+            callback.onResult(list);
+        } else {
+            invalidate();
+        }
+    }
+
+    /**
+     * Return the rows from startPos to startPos + loadCount
+     */
+    @Nullable
+    public List<T> loadRange(int startPosition, int loadCount) {
+        final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mLimitOffsetQuery,
+                mSourceQuery.getArgCount() + 2);
+        sqLiteQuery.copyArgumentsFrom(mSourceQuery);
+        sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
+        sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
+        if (mInTransaction) {
+            mDb.beginTransaction();
+            Cursor cursor = null;
+            try {
+                cursor = mDb.query(sqLiteQuery);
+                List<T> rows = convertRows(cursor);
+                mDb.setTransactionSuccessful();
+                return rows;
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+                mDb.endTransaction();
+                sqLiteQuery.release();
+            }
+        } else {
+            Cursor cursor = mDb.query(sqLiteQuery);
+            //noinspection TryFinallyCanBeTryWithResources
+            try {
+                return convertRows(cursor);
+            } finally {
+                cursor.close();
+                sqLiteQuery.release();
+            }
+        }
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/util/StringUtil.java b/room/runtime/src/main/java/androidx/room/util/StringUtil.java
new file mode 100644
index 0000000..8988490
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/util/StringUtil.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.util;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * @hide
+ *
+ * String utilities for Room
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class StringUtil {
+
+    @SuppressWarnings("unused")
+    public static final String[] EMPTY_STRING_ARRAY = new String[0];
+    /**
+     * Returns a new StringBuilder to be used while producing SQL queries.
+     *
+     * @return A new or recycled StringBuilder
+     */
+    public static StringBuilder newStringBuilder() {
+        // TODO pool:
+        return new StringBuilder();
+    }
+
+    /**
+     * Adds bind variable placeholders (?) to the given string. Each placeholder is separated
+     * by a comma.
+     *
+     * @param builder The StringBuilder for the query
+     * @param count Number of placeholders
+     */
+    public static void appendPlaceholders(StringBuilder builder, int count) {
+        for (int i = 0; i < count; i++) {
+            builder.append("?");
+            if (i < count - 1) {
+                builder.append(",");
+            }
+        }
+    }
+    /**
+     * Splits a comma separated list of integers to integer list.
+     * <p>
+     * If an input is malformed, it is omitted from the result.
+     *
+     * @param input Comma separated list of integers.
+     * @return A List containing the integers or null if the input is null.
+     */
+    @Nullable
+    public static List<Integer> splitToIntList(@Nullable String input) {
+        if (input == null) {
+            return null;
+        }
+        List<Integer> result = new ArrayList<>();
+        StringTokenizer tokenizer = new StringTokenizer(input, ",");
+        while (tokenizer.hasMoreElements()) {
+            final String item = tokenizer.nextToken();
+            try {
+                result.add(Integer.parseInt(item));
+            } catch (NumberFormatException ex) {
+                Log.e("ROOM", "Malformed integer list", ex);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Joins the given list of integers into a comma separated list.
+     *
+     * @param input The list of integers.
+     * @return Comma separated string composed of integers in the list. If the list is null, return
+     * value is null.
+     */
+    @Nullable
+    public static String joinIntoString(@Nullable List<Integer> input) {
+        if (input == null) {
+            return null;
+        }
+
+        final int size = input.size();
+        if (size == 0) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < size; i++) {
+            sb.append(Integer.toString(input.get(i)));
+            if (i < size - 1) {
+                sb.append(",");
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/room/runtime/src/main/java/androidx/room/util/TableInfo.java b/room/runtime/src/main/java/androidx/room/util/TableInfo.java
new file mode 100644
index 0000000..819c726
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/util/TableInfo.java
@@ -0,0 +1,592 @@
+/*
+ * 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 androidx.room.util;
+
+import android.database.Cursor;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.room.ColumnInfo;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * A data class that holds the information about a table.
+ * <p>
+ * It directly maps to the result of {@code PRAGMA table_info(<table_name>)}. Check the
+ * <a href="http://www.sqlite.org/pragma.html#pragma_table_info">PRAGMA table_info</a>
+ * documentation for more details.
+ * <p>
+ * Even though SQLite column names are case insensitive, this class uses case sensitive matching.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources",
+        "SimplifiableIfStatement"})
+// if you change this class, you must change TableInfoWriter.kt
+public class TableInfo {
+    /**
+     * The table name.
+     */
+    public final String name;
+    /**
+     * Unmodifiable map of columns keyed by column name.
+     */
+    public final Map<String, Column> columns;
+
+    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,
+            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
+                + '}';
+    }
+
+    /**
+     * Reads the table information from the given database.
+     *
+     * @param database  The database to read the information from.
+     * @param tableName The table name.
+     * @return A TableInfo containing the schema information for the provided table name.
+     */
+    @SuppressWarnings("SameParameterValue")
+    public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
+        Map<String, Column> columns = readColumns(database, tableName);
+        Set<ForeignKey> foreignKeys = readForeignKeys(database, tableName);
+        Set<Index> indices = readIndices(database, tableName);
+        return new TableInfo(tableName, columns, foreignKeys, indices);
+    }
+
+    private static Set<ForeignKey> readForeignKeys(SupportSQLiteDatabase database,
+            String tableName) {
+        Set<ForeignKey> foreignKeys = new HashSet<>();
+        // this seems to return everything in order but it is not documented so better be safe
+        Cursor cursor = database.query("PRAGMA foreign_key_list(`" + tableName + "`)");
+        try {
+            final int idColumnIndex = cursor.getColumnIndex("id");
+            final int seqColumnIndex = cursor.getColumnIndex("seq");
+            final int tableColumnIndex = cursor.getColumnIndex("table");
+            final int onDeleteColumnIndex = cursor.getColumnIndex("on_delete");
+            final int onUpdateColumnIndex = cursor.getColumnIndex("on_update");
+
+            final List<ForeignKeyWithSequence> ordered = readForeignKeyFieldMappings(cursor);
+            final int count = cursor.getCount();
+            for (int position = 0; position < count; position++) {
+                cursor.moveToPosition(position);
+                final int seq = cursor.getInt(seqColumnIndex);
+                if (seq != 0) {
+                    continue;
+                }
+                final int id = cursor.getInt(idColumnIndex);
+                List<String> myColumns = new ArrayList<>();
+                List<String> refColumns = new ArrayList<>();
+                for (ForeignKeyWithSequence key : ordered) {
+                    if (key.mId == id) {
+                        myColumns.add(key.mFrom);
+                        refColumns.add(key.mTo);
+                    }
+                }
+                foreignKeys.add(new ForeignKey(
+                        cursor.getString(tableColumnIndex),
+                        cursor.getString(onDeleteColumnIndex),
+                        cursor.getString(onUpdateColumnIndex),
+                        myColumns,
+                        refColumns
+                ));
+            }
+        } finally {
+            cursor.close();
+        }
+        return foreignKeys;
+    }
+
+    private static List<ForeignKeyWithSequence> readForeignKeyFieldMappings(Cursor cursor) {
+        final int idColumnIndex = cursor.getColumnIndex("id");
+        final int seqColumnIndex = cursor.getColumnIndex("seq");
+        final int fromColumnIndex = cursor.getColumnIndex("from");
+        final int toColumnIndex = cursor.getColumnIndex("to");
+        final int count = cursor.getCount();
+        List<ForeignKeyWithSequence> result = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            cursor.moveToPosition(i);
+            result.add(new ForeignKeyWithSequence(
+                    cursor.getInt(idColumnIndex),
+                    cursor.getInt(seqColumnIndex),
+                    cursor.getString(fromColumnIndex),
+                    cursor.getString(toColumnIndex)
+            ));
+        }
+        Collections.sort(result);
+        return result;
+    }
+
+    private static Map<String, Column> readColumns(SupportSQLiteDatabase database,
+            String tableName) {
+        Cursor cursor = database
+                .query("PRAGMA table_info(`" + tableName + "`)");
+        //noinspection TryFinallyCanBeTryWithResources
+        Map<String, Column> columns = new HashMap<>();
+        try {
+            if (cursor.getColumnCount() > 0) {
+                int nameIndex = cursor.getColumnIndex("name");
+                int typeIndex = cursor.getColumnIndex("type");
+                int notNullIndex = cursor.getColumnIndex("notnull");
+                int pkIndex = cursor.getColumnIndex("pk");
+
+                while (cursor.moveToNext()) {
+                    final String name = cursor.getString(nameIndex);
+                    final String type = cursor.getString(typeIndex);
+                    final boolean notNull = 0 != cursor.getInt(notNullIndex);
+                    final int primaryKeyPosition = cursor.getInt(pkIndex);
+                    columns.put(name, new Column(name, type, notNull, primaryKeyPosition));
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        return columns;
+    }
+
+    /**
+     * @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();
+        }
+    }
+
+    /**
+     * @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<>();
+
+            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();
+        }
+    }
+
+    /**
+     * Holds the information about a database column.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class Column {
+        /**
+         * The column name.
+         */
+        public final String name;
+        /**
+         * The column type affinity.
+         */
+        public final String type;
+        /**
+         * The column type after it is normalized to one of the basic types according to
+         * https://www.sqlite.org/datatype3.html Section 3.1.
+         * <p>
+         * This is the value Room uses for equality check.
+         */
+        @ColumnInfo.SQLiteTypeAffinity
+        public final int affinity;
+        /**
+         * Whether or not the column can be NULL.
+         */
+        public final boolean notNull;
+        /**
+         * The position of the column in the list of primary keys, 0 if the column is not part
+         * of the primary key.
+         * <p>
+         * This information is only available in API 20+.
+         * <a href="https://www.sqlite.org/releaselog/3_7_16_2.html">(SQLite version 3.7.16.2)</a>
+         * On older platforms, it will be 1 if the column is part of the primary key and 0
+         * otherwise.
+         * <p>
+         * The {@link #equals(Object)} implementation handles this inconsistency based on
+         * API levels os if you are using a custom SQLite deployment, it may return false
+         * positives.
+         */
+        public final int primaryKeyPosition;
+
+        // if you change this constructor, you must change TableInfoWriter.kt
+        public Column(String name, String type, boolean notNull, int primaryKeyPosition) {
+            this.name = name;
+            this.type = type;
+            this.notNull = notNull;
+            this.primaryKeyPosition = primaryKeyPosition;
+            this.affinity = findAffinity(type);
+        }
+
+        /**
+         * Implements https://www.sqlite.org/datatype3.html section 3.1
+         *
+         * @param type The type that was given to the sqlite
+         * @return The normalized type which is one of the 5 known affinities
+         */
+        @ColumnInfo.SQLiteTypeAffinity
+        private static int findAffinity(@Nullable String type) {
+            if (type == null) {
+                return ColumnInfo.BLOB;
+            }
+            String uppercaseType = type.toUpperCase(Locale.US);
+            if (uppercaseType.contains("INT")) {
+                return ColumnInfo.INTEGER;
+            }
+            if (uppercaseType.contains("CHAR")
+                    || uppercaseType.contains("CLOB")
+                    || uppercaseType.contains("TEXT")) {
+                return ColumnInfo.TEXT;
+            }
+            if (uppercaseType.contains("BLOB")) {
+                return ColumnInfo.BLOB;
+            }
+            if (uppercaseType.contains("REAL")
+                    || uppercaseType.contains("FLOA")
+                    || uppercaseType.contains("DOUB")) {
+                return ColumnInfo.REAL;
+            }
+            // sqlite returns NUMERIC here but it is like a catch all. We already
+            // have UNDEFINED so it is better to use UNDEFINED for consistency.
+            return ColumnInfo.UNDEFINED;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Column column = (Column) o;
+            if (Build.VERSION.SDK_INT >= 20) {
+                if (primaryKeyPosition != column.primaryKeyPosition) return false;
+            } else {
+                if (isPrimaryKey() != column.isPrimaryKey()) return false;
+            }
+
+            if (!name.equals(column.name)) return false;
+            //noinspection SimplifiableIfStatement
+            if (notNull != column.notNull) return false;
+            return affinity == column.affinity;
+        }
+
+        /**
+         * Returns whether this column is part of the primary key or not.
+         *
+         * @return True if this column is part of the primary key, false otherwise.
+         */
+        public boolean isPrimaryKey() {
+            return primaryKeyPosition > 0;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = name.hashCode();
+            result = 31 * result + affinity;
+            result = 31 * result + (notNull ? 1231 : 1237);
+            result = 31 * result + primaryKeyPosition;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Column{"
+                    + "name='" + name + '\''
+                    + ", type='" + type + '\''
+                    + ", affinity='" + affinity + '\''
+                    + ", notNull=" + notNull
+                    + ", primaryKeyPosition=" + primaryKeyPosition
+                    + '}';
+        }
+    }
+
+    /**
+     * Holds the information about an SQLite foreign key
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static class ForeignKey {
+        @NonNull
+        public final String referenceTable;
+        @NonNull
+        public final String onDelete;
+        @NonNull
+        public final String onUpdate;
+        @NonNull
+        public final List<String> columnNames;
+        @NonNull
+        public final List<String> referenceColumnNames;
+
+        public ForeignKey(@NonNull String referenceTable, @NonNull String onDelete,
+                @NonNull String onUpdate,
+                @NonNull List<String> columnNames, @NonNull List<String> referenceColumnNames) {
+            this.referenceTable = referenceTable;
+            this.onDelete = onDelete;
+            this.onUpdate = onUpdate;
+            this.columnNames = Collections.unmodifiableList(columnNames);
+            this.referenceColumnNames = Collections.unmodifiableList(referenceColumnNames);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ForeignKey that = (ForeignKey) o;
+
+            if (!referenceTable.equals(that.referenceTable)) return false;
+            if (!onDelete.equals(that.onDelete)) return false;
+            if (!onUpdate.equals(that.onUpdate)) return false;
+            //noinspection SimplifiableIfStatement
+            if (!columnNames.equals(that.columnNames)) return false;
+            return referenceColumnNames.equals(that.referenceColumnNames);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = referenceTable.hashCode();
+            result = 31 * result + onDelete.hashCode();
+            result = 31 * result + onUpdate.hashCode();
+            result = 31 * result + columnNames.hashCode();
+            result = 31 * result + referenceColumnNames.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "ForeignKey{"
+                    + "referenceTable='" + referenceTable + '\''
+                    + ", onDelete='" + onDelete + '\''
+                    + ", onUpdate='" + onUpdate + '\''
+                    + ", columnNames=" + columnNames
+                    + ", referenceColumnNames=" + referenceColumnNames
+                    + '}';
+        }
+    }
+
+    /**
+     * Temporary data holder for a foreign key row in the pragma result. We need this to ensure
+     * sorting in the generated foreign key object.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    static class ForeignKeyWithSequence implements Comparable<ForeignKeyWithSequence> {
+        final int mId;
+        final int mSequence;
+        final String mFrom;
+        final String mTo;
+
+        ForeignKeyWithSequence(int id, int sequence, String from, String to) {
+            mId = id;
+            mSequence = sequence;
+            mFrom = from;
+            mTo = to;
+        }
+
+        @Override
+        public int compareTo(@NonNull ForeignKeyWithSequence o) {
+            final int idCmp = mId - o.mId;
+            if (idCmp == 0) {
+                return mSequence - o.mSequence;
+            } else {
+                return idCmp;
+            }
+        }
+    }
+
+    /**
+     * 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
+                    + '}';
+        }
+    }
+}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
deleted file mode 100644
index 215bc30..0000000
--- a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import static org.hamcrest.CoreMatchers.instanceOf;
-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 static org.mockito.Mockito.mock;
-
-import static java.util.Arrays.asList;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
-import android.arch.persistence.room.migration.Migration;
-import android.content.Context;
-import android.support.annotation.NonNull;
-
-import org.hamcrest.CoreMatchers;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.List;
-
-@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
-@RunWith(JUnit4.class)
-public class BuilderTest {
-    @Test(expected = IllegalArgumentException.class)
-    public void nullContext() {
-        //noinspection ConstantConditions
-        Room.databaseBuilder(null, RoomDatabase.class, "bla").build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void nullContext2() {
-        //noinspection ConstantConditions
-        Room.inMemoryDatabaseBuilder(null, RoomDatabase.class).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void nullName() {
-        //noinspection ConstantConditions
-        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, null).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void emptyName() {
-        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, "  ").build();
-    }
-
-    @Test
-    public void migration() {
-        Migration m1 = new EmptyMigration(0, 1);
-        Migration m2 = new EmptyMigration(1, 2);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1, m2).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(0, 1), is(asList(m1)));
-        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
-        assertThat(migrations.findMigrationPath(0, 2), is(asList(m1, m2)));
-        assertThat(migrations.findMigrationPath(2, 0), CoreMatchers.<List<Migration>>nullValue());
-        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
-    }
-
-    @Test
-    public void migrationOverride() {
-        Migration m1 = new EmptyMigration(0, 1);
-        Migration m2 = new EmptyMigration(1, 2);
-        Migration m3 = new EmptyMigration(0, 1);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1, m2, m3).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(0, 1), is(asList(m3)));
-        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
-        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
-    }
-
-    @Test
-    public void migrationJump() {
-        Migration m1 = new EmptyMigration(0, 1);
-        Migration m2 = new EmptyMigration(1, 2);
-        Migration m3 = new EmptyMigration(2, 3);
-        Migration m4 = new EmptyMigration(0, 3);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1, m2, m3, m4).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(0, 3), is(asList(m4)));
-        assertThat(migrations.findMigrationPath(1, 3), is(asList(m2, m3)));
-    }
-
-    @Test
-    public void migrationDowngrade() {
-        Migration m1_2 = new EmptyMigration(1, 2);
-        Migration m2_3 = new EmptyMigration(2, 3);
-        Migration m3_4 = new EmptyMigration(3, 4);
-        Migration m3_2 = new EmptyMigration(3, 2);
-        Migration m2_1 = new EmptyMigration(2, 1);
-        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
-                .addMigrations(m1_2, m2_3, m3_4, m3_2, m2_1).build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
-        assertThat(migrations.findMigrationPath(3, 2), is(asList(m3_2)));
-        assertThat(migrations.findMigrationPath(3, 1), is(asList(m3_2, m2_1)));
-    }
-
-    @Test
-    public void skipMigration() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigration()
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.requireMigration, is(false));
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationFrom_calledOnce_migrationsNotRequiredForValues() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 2).build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(2), is(false));
-    }
-
-    @Test
-    public void fallbackToDestructiveMigrationFrom_calledTwice_migrationsNotRequiredForValues() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 2)
-                .fallbackToDestructiveMigrationFrom(3, 4)
-                .build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(2), is(false));
-        assertThat(config.isMigrationRequiredFrom(3), is(false));
-        assertThat(config.isMigrationRequiredFrom(4), is(false));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_fallBackToDestructiveCalled_alwaysReturnsFalse() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigration()
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(0), is(false));
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(5), is(false));
-        assertThat(config.isMigrationRequiredFrom(12), is(false));
-        assertThat(config.isMigrationRequiredFrom(132), is(false));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_byDefault_alwaysReturnsTrue() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(0), is(true));
-        assertThat(config.isMigrationRequiredFrom(1), is(true));
-        assertThat(config.isMigrationRequiredFrom(5), is(true));
-        assertThat(config.isMigrationRequiredFrom(12), is(true));
-        assertThat(config.isMigrationRequiredFrom(132), is(true));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_fallBackToDestFromCalled_falseForProvidedValues() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 4, 81)
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(1), is(false));
-        assertThat(config.isMigrationRequiredFrom(4), is(false));
-        assertThat(config.isMigrationRequiredFrom(81), is(false));
-    }
-
-    @Test
-    public void isMigrationRequiredFrom_fallBackToDestFromCalled_trueForNonProvidedValues() {
-        Context context = mock(Context.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .fallbackToDestructiveMigrationFrom(1, 4, 81)
-                .build();
-
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.isMigrationRequiredFrom(2), is(true));
-        assertThat(config.isMigrationRequiredFrom(3), is(true));
-        assertThat(config.isMigrationRequiredFrom(73), is(true));
-    }
-
-    @Test
-    public void createBasic() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
-        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config, notNullValue());
-        assertThat(config.context, is(context));
-        assertThat(config.name, is(nullValue()));
-        assertThat(config.allowMainThreadQueries, is(false));
-        assertThat(config.journalMode, is(RoomDatabase.JournalMode.TRUNCATE));
-        assertThat(config.sqliteOpenHelperFactory,
-                instanceOf(FrameworkSQLiteOpenHelperFactory.class));
-    }
-
-    @Test
-    public void createAllowMainThread() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .allowMainThreadQueries()
-                .build();
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.allowMainThreadQueries, is(true));
-    }
-
-    @Test
-    public void createWriteAheadLogging() {
-        Context context = mock(Context.class);
-        TestDatabase db = Room.databaseBuilder(context, TestDatabase.class, "foo")
-                .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING).build();
-        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config.journalMode, is(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING));
-    }
-
-    @Test
-    public void createWithFactoryAndVersion() {
-        Context context = mock(Context.class);
-        SupportSQLiteOpenHelper.Factory factory = mock(SupportSQLiteOpenHelper.Factory.class);
-
-        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
-                .openHelperFactory(factory)
-                .build();
-        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
-        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
-        assertThat(config, notNullValue());
-        assertThat(config.sqliteOpenHelperFactory, is(factory));
-    }
-
-    abstract static class TestDatabase extends RoomDatabase {
-    }
-
-    static class EmptyMigration extends Migration {
-        EmptyMigration(int start, int end) {
-            super(start, end);
-        }
-
-        @Override
-        public void migrate(@NonNull SupportSQLiteDatabase database) {
-        }
-    }
-
-}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest_TestDatabase_Impl.java b/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest_TestDatabase_Impl.java
deleted file mode 100644
index 8e310e2..0000000
--- a/room/runtime/src/test/java/android/arch/persistence/room/BuilderTest_TestDatabase_Impl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-
-public class BuilderTest_TestDatabase_Impl extends BuilderTest.TestDatabase {
-    DatabaseConfiguration mConfig;
-    @Override
-    public void init(DatabaseConfiguration configuration) {
-        super.init(configuration);
-        mConfig = configuration;
-    }
-
-    @Override
-    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {
-        return null;
-    }
-
-    @Override
-    protected InvalidationTracker createInvalidationTracker() {
-        return null;
-    }
-
-    @Override
-    public void clearAllTables() {
-    }
-}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java b/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java
deleted file mode 100644
index ca091d5..0000000
--- a/room/runtime/src/test/java/android/arch/persistence/room/InvalidationTrackerTest.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsCollectionContaining.hasItem;
-import static org.hamcrest.core.IsCollectionContaining.hasItems;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.arch.core.executor.JunitTaskExecutorRule;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.support.annotation.NonNull;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReentrantLock;
-
-@RunWith(JUnit4.class)
-public class InvalidationTrackerTest {
-    private InvalidationTracker mTracker;
-    private RoomDatabase mRoomDatabase;
-    private SupportSQLiteOpenHelper mOpenHelper;
-    @Rule
-    public JunitTaskExecutorRule mTaskExecutorRule = new JunitTaskExecutorRule(1, true);
-
-    @Before
-    public void setup() {
-        mRoomDatabase = mock(RoomDatabase.class);
-        SupportSQLiteDatabase sqliteDb = mock(SupportSQLiteDatabase.class);
-        final SupportSQLiteStatement statement = mock(SupportSQLiteStatement.class);
-        mOpenHelper = mock(SupportSQLiteOpenHelper.class);
-
-        doReturn(statement).when(sqliteDb).compileStatement(eq(InvalidationTracker.CLEANUP_SQL));
-        doReturn(sqliteDb).when(mOpenHelper).getWritableDatabase();
-        doReturn(true).when(mRoomDatabase).isOpen();
-        ReentrantLock closeLock = new ReentrantLock();
-        doReturn(closeLock).when(mRoomDatabase).getCloseLock();
-        //noinspection ResultOfMethodCallIgnored
-        doReturn(mOpenHelper).when(mRoomDatabase).getOpenHelper();
-
-        mTracker = new InvalidationTracker(mRoomDatabase, "a", "B", "i");
-        mTracker.internalInit(sqliteDb);
-    }
-
-    @Before
-    public void setLocale() {
-        Locale.setDefault(Locale.forLanguageTag("tr-TR"));
-    }
-
-    @After
-    public void unsetLocale() {
-        Locale.setDefault(Locale.US);
-    }
-
-    @Test
-    public void tableIds() {
-        assertThat(mTracker.mTableIdLookup.get("a"), is(0));
-        assertThat(mTracker.mTableIdLookup.get("b"), is(1));
-    }
-
-    @Test
-    public void testWeak() throws InterruptedException {
-        final AtomicInteger data = new AtomicInteger(0);
-        InvalidationTracker.Observer observer = new InvalidationTracker.Observer("a") {
-            @Override
-            public void onInvalidated(@NonNull Set<String> tables) {
-                data.incrementAndGet();
-            }
-        };
-        mTracker.addWeakObserver(observer);
-        setVersions(1, 0);
-        refreshSync();
-        assertThat(data.get(), is(1));
-        observer = null;
-        forceGc();
-        setVersions(2, 0);
-        refreshSync();
-        assertThat(data.get(), is(1));
-    }
-
-    @Test
-    public void addRemoveObserver() throws Exception {
-        InvalidationTracker.Observer observer = new LatchObserver(1, "a");
-        mTracker.addObserver(observer);
-        assertThat(mTracker.mObserverMap.size(), is(1));
-        mTracker.removeObserver(new LatchObserver(1, "a"));
-        assertThat(mTracker.mObserverMap.size(), is(1));
-        mTracker.removeObserver(observer);
-        assertThat(mTracker.mObserverMap.size(), is(0));
-    }
-
-    private void drainTasks() throws InterruptedException {
-        mTaskExecutorRule.drainTasks(200);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void badObserver() {
-        InvalidationTracker.Observer observer = new LatchObserver(1, "x");
-        mTracker.addObserver(observer);
-    }
-
-    @Test
-    public void refreshReadValues() throws Exception {
-        setVersions(1, 0, 2, 1);
-        refreshSync();
-        assertThat(mTracker.mTableVersions, is(new long[]{1, 2, 0}));
-
-        setVersions(3, 1);
-        refreshSync();
-        assertThat(mTracker.mTableVersions, is(new long[]{1, 3, 0}));
-
-        setVersions(7, 0);
-        refreshSync();
-        assertThat(mTracker.mTableVersions, is(new long[]{7, 3, 0}));
-
-        refreshSync();
-        assertThat(mTracker.mTableVersions, is(new long[]{7, 3, 0}));
-    }
-
-    private void refreshSync() throws InterruptedException {
-        mTracker.refreshVersionsAsync();
-        drainTasks();
-    }
-
-    @Test
-    public void refreshCheckTasks() throws Exception {
-        when(mRoomDatabase.query(anyString(), any(Object[].class)))
-                .thenReturn(mock(Cursor.class));
-        mTracker.refreshVersionsAsync();
-        mTracker.refreshVersionsAsync();
-        verify(mTaskExecutorRule.getTaskExecutor()).executeOnDiskIO(mTracker.mRefreshRunnable);
-        drainTasks();
-
-        reset(mTaskExecutorRule.getTaskExecutor());
-        mTracker.refreshVersionsAsync();
-        verify(mTaskExecutorRule.getTaskExecutor()).executeOnDiskIO(mTracker.mRefreshRunnable);
-    }
-
-    @Test
-    public void observe1Table() throws Exception {
-        LatchObserver observer = new LatchObserver(1, "a");
-        mTracker.addObserver(observer);
-        setVersions(1, 0, 2, 1);
-        refreshSync();
-        assertThat(observer.await(), is(true));
-        assertThat(observer.getInvalidatedTables().size(), is(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("a"));
-
-        setVersions(3, 1);
-        observer.reset(1);
-        refreshSync();
-        assertThat(observer.await(), is(false));
-
-        setVersions(4, 0);
-        refreshSync();
-        assertThat(observer.await(), is(true));
-        assertThat(observer.getInvalidatedTables().size(), is(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("a"));
-    }
-
-    @Test
-    public void observe2Tables() throws Exception {
-        LatchObserver observer = new LatchObserver(1, "A", "B");
-        mTracker.addObserver(observer);
-        setVersions(1, 0, 2, 1);
-        refreshSync();
-        assertThat(observer.await(), is(true));
-        assertThat(observer.getInvalidatedTables().size(), is(2));
-        assertThat(observer.getInvalidatedTables(), hasItems("A", "B"));
-
-        setVersions(3, 1);
-        observer.reset(1);
-        refreshSync();
-        assertThat(observer.await(), is(true));
-        assertThat(observer.getInvalidatedTables().size(), is(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("B"));
-
-        setVersions(4, 0);
-        observer.reset(1);
-        refreshSync();
-        assertThat(observer.await(), is(true));
-        assertThat(observer.getInvalidatedTables().size(), is(1));
-        assertThat(observer.getInvalidatedTables(), hasItem("A"));
-
-        observer.reset(1);
-        refreshSync();
-        assertThat(observer.await(), is(false));
-    }
-
-    @Test
-    public void locale() {
-        LatchObserver observer = new LatchObserver(1, "I");
-        mTracker.addObserver(observer);
-    }
-
-    @Test
-    public void closedDb() {
-        doReturn(false).when(mRoomDatabase).isOpen();
-        doThrow(new IllegalStateException("foo")).when(mOpenHelper).getWritableDatabase();
-        mTracker.addObserver(new LatchObserver(1, "a", "b"));
-        mTracker.mRefreshRunnable.run();
-    }
-
-    // @Test - disabled due to flakiness b/65257997
-    public void closedDbAfterOpen() throws InterruptedException {
-        setVersions(3, 1);
-        mTracker.addObserver(new LatchObserver(1, "a", "b"));
-        mTracker.syncTriggers();
-        mTracker.mRefreshRunnable.run();
-        doThrow(new SQLiteException("foo")).when(mRoomDatabase).query(
-                Mockito.eq(InvalidationTracker.SELECT_UPDATED_TABLES_SQL),
-                any(Object[].class));
-        mTracker.mPendingRefresh.set(true);
-        mTracker.mRefreshRunnable.run();
-    }
-
-    /**
-     * Key value pairs of VERSION, TABLE_ID
-     */
-    private void setVersions(int... keyValuePairs) throws InterruptedException {
-        // mockito does not like multi-threaded access so before setting versions, make sure we
-        // sync background tasks.
-        drainTasks();
-        Cursor cursor = createCursorWithValues(keyValuePairs);
-        doReturn(cursor).when(mRoomDatabase).query(
-                Mockito.eq(InvalidationTracker.SELECT_UPDATED_TABLES_SQL),
-                any(Object[].class)
-        );
-    }
-
-    private Cursor createCursorWithValues(final int... keyValuePairs) {
-        Cursor cursor = mock(Cursor.class);
-        final AtomicInteger index = new AtomicInteger(-2);
-        when(cursor.moveToNext()).thenAnswer(new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) throws Throwable {
-                return index.addAndGet(2) < keyValuePairs.length;
-            }
-        });
-        Answer<Integer> intAnswer = new Answer<Integer>() {
-            @Override
-            public Integer answer(InvocationOnMock invocation) throws Throwable {
-                return keyValuePairs[index.intValue() + (Integer) invocation.getArguments()[0]];
-            }
-        };
-        Answer<Long> longAnswer = new Answer<Long>() {
-            @Override
-            public Long answer(InvocationOnMock invocation) throws Throwable {
-                return (long) keyValuePairs[index.intValue()
-                        + (Integer) invocation.getArguments()[0]];
-            }
-        };
-        when(cursor.getInt(anyInt())).thenAnswer(intAnswer);
-        when(cursor.getLong(anyInt())).thenAnswer(longAnswer);
-        return cursor;
-    }
-
-    static class LatchObserver extends InvalidationTracker.Observer {
-        private CountDownLatch mLatch;
-        private Set<String> mInvalidatedTables;
-
-        LatchObserver(int count, String... tableNames) {
-            super(tableNames);
-            mLatch = new CountDownLatch(count);
-        }
-
-        boolean await() throws InterruptedException {
-            return mLatch.await(3, TimeUnit.SECONDS);
-        }
-
-        @Override
-        public void onInvalidated(@NonNull Set<String> tables) {
-            mInvalidatedTables = tables;
-            mLatch.countDown();
-        }
-
-        void reset(@SuppressWarnings("SameParameterValue") int count) {
-            mInvalidatedTables = null;
-            mLatch = new CountDownLatch(count);
-        }
-
-        Set<String> getInvalidatedTables() {
-            return mInvalidatedTables;
-        }
-    }
-
-    private static void forceGc() {
-        // Use a random index in the list to detect the garbage collection each time because
-        // .get() may accidentally trigger a strong reference during collection.
-        ArrayList<WeakReference<byte[]>> leak = new ArrayList<>();
-        do {
-            WeakReference<byte[]> arr = new WeakReference<>(new byte[100]);
-            leak.add(arr);
-        } while (leak.get((int) (Math.random() * leak.size())).get() != null);
-    }
-}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/ObservedTableTrackerTest.java b/room/runtime/src/test/java/android/arch/persistence/room/ObservedTableTrackerTest.java
deleted file mode 100644
index ffddee9..0000000
--- a/room/runtime/src/test/java/android/arch/persistence/room/ObservedTableTrackerTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package android.arch.persistence.room;
-
-
-import static android.arch.persistence.room.InvalidationTracker.ObservedTableTracker.ADD;
-import static android.arch.persistence.room.InvalidationTracker.ObservedTableTracker.NO_OP;
-import static android.arch.persistence.room.InvalidationTracker.ObservedTableTracker.REMOVE;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-
-@RunWith(JUnit4.class)
-public class ObservedTableTrackerTest {
-    private static final int TABLE_COUNT = 5;
-    private InvalidationTracker.ObservedTableTracker mTracker;
-
-    @Before
-    public void setup() {
-        mTracker = new InvalidationTracker.ObservedTableTracker(TABLE_COUNT);
-    }
-
-    @Test
-    public void basicAdd() {
-        mTracker.onAdded(2, 3);
-        assertThat(mTracker.getTablesToSync(), is(createResponse(2, ADD, 3, ADD)));
-    }
-
-    @Test
-    public void basicRemove() {
-        initState(2, 3);
-        mTracker.onRemoved(3);
-        assertThat(mTracker.getTablesToSync(), is(createResponse(3, REMOVE)));
-    }
-
-    @Test
-    public void noChange() {
-        initState(1, 3);
-        mTracker.onAdded(3);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-    }
-
-    @Test
-    public void returnNullUntilSync() {
-        initState(1, 3);
-        mTracker.onAdded(4);
-        assertThat(mTracker.getTablesToSync(), is(createResponse(4, ADD)));
-        mTracker.onAdded(0);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onSyncCompleted();
-        assertThat(mTracker.getTablesToSync(), is(createResponse(0, ADD)));
-    }
-
-    @Test
-    public void multipleAdditionsDeletions() {
-        initState(2, 4);
-        mTracker.onAdded(2);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onAdded(2, 4);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onRemoved(2);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onRemoved(2, 4);
-        assertThat(mTracker.getTablesToSync(), is(nullValue()));
-        mTracker.onAdded(1, 3);
-        mTracker.onRemoved(2, 4);
-        assertThat(mTracker.getTablesToSync(), is(
-                createResponse(1, ADD, 2, REMOVE, 3, ADD, 4, REMOVE)));
-    }
-
-    private void initState(int... tableIds) {
-        mTracker.onAdded(tableIds);
-        mTracker.getTablesToSync();
-        mTracker.onSyncCompleted();
-    }
-
-    private static int[] createResponse(int... tuples) {
-        int[] result = new int[TABLE_COUNT];
-        Arrays.fill(result, NO_OP);
-        for (int i = 0; i < tuples.length; i += 2) {
-            result[tuples[i]] = tuples[i + 1];
-        }
-        return result;
-    }
-}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/RoomSQLiteQueryTest.java b/room/runtime/src/test/java/android/arch/persistence/room/RoomSQLiteQueryTest.java
deleted file mode 100644
index e7a8644..0000000
--- a/room/runtime/src/test/java/android/arch/persistence/room/RoomSQLiteQueryTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.sameInstance;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.arch.persistence.db.SupportSQLiteProgram;
-
-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.Iterator;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class RoomSQLiteQueryTest {
-    @Before
-    public void clear() {
-        RoomSQLiteQuery.sQueryPool.clear();
-    }
-
-    @Test
-    public void acquireBasic() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        assertThat(query.getSql(), is("abc"));
-        assertThat(query.mArgCount, is(3));
-        assertThat(query.mBlobBindings.length, is(4));
-        assertThat(query.mLongBindings.length, is(4));
-        assertThat(query.mStringBindings.length, is(4));
-        assertThat(query.mDoubleBindings.length, is(4));
-    }
-
-    @Test
-    public void acquireSameSizeAgain() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        query.release();
-        assertThat(RoomSQLiteQuery.acquire("blah", 3), sameInstance(query));
-    }
-
-    @Test
-    public void acquireSameSizeWithoutRelease() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        assertThat(RoomSQLiteQuery.acquire("fda", 3), not(sameInstance(query)));
-    }
-
-    @Test
-    public void bindings() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 6);
-        byte[] myBlob = new byte[3];
-        long myLong = 3L;
-        double myDouble = 7.0;
-        String myString = "ss";
-        query.bindBlob(1, myBlob);
-        query.bindLong(2, myLong);
-        query.bindNull(3);
-        query.bindDouble(4, myDouble);
-        query.bindString(5, myString);
-        query.bindNull(6);
-        SupportSQLiteProgram program = mock(SupportSQLiteProgram.class);
-        query.bindTo(program);
-
-        verify(program).bindBlob(1, myBlob);
-        verify(program).bindLong(2, myLong);
-        verify(program).bindNull(3);
-        verify(program).bindDouble(4, myDouble);
-        verify(program).bindString(5, myString);
-        verify(program).bindNull(6);
-    }
-
-    @Test
-    public void dontKeepSameSizeTwice() {
-        RoomSQLiteQuery query1 = RoomSQLiteQuery.acquire("abc", 3);
-        RoomSQLiteQuery query2 = RoomSQLiteQuery.acquire("zx", 3);
-        RoomSQLiteQuery query3 = RoomSQLiteQuery.acquire("qw", 0);
-
-        query1.release();
-        query2.release();
-        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(1));
-
-        query3.release();
-        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(2));
-    }
-
-    @Test
-    public void returnExistingForSmallerSize() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        query.release();
-        assertThat(RoomSQLiteQuery.acquire("dsa", 2), sameInstance(query));
-    }
-
-    @Test
-    public void returnNewForBigger() {
-        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
-        query.release();
-        assertThat(RoomSQLiteQuery.acquire("dsa", 4), not(sameInstance(query)));
-    }
-
-    @Test
-    public void pruneCache() {
-        for (int i = 0; i < RoomSQLiteQuery.POOL_LIMIT; i++) {
-            RoomSQLiteQuery.acquire("dsdsa", i).release();
-        }
-        pruneCacheTest();
-    }
-
-    @Test
-    public void pruneCacheReverseInsertion() {
-        List<RoomSQLiteQuery> queries = new ArrayList<>();
-        for (int i = RoomSQLiteQuery.POOL_LIMIT - 1; i >= 0; i--) {
-            queries.add(RoomSQLiteQuery.acquire("dsdsa", i));
-        }
-        for (RoomSQLiteQuery query : queries) {
-            query.release();
-        }
-        pruneCacheTest();
-    }
-
-    private void pruneCacheTest() {
-        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(RoomSQLiteQuery.POOL_LIMIT));
-        RoomSQLiteQuery.acquire("dsadsa", RoomSQLiteQuery.POOL_LIMIT + 1).release();
-        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(RoomSQLiteQuery.DESIRED_POOL_SIZE));
-        Iterator<RoomSQLiteQuery> itr = RoomSQLiteQuery.sQueryPool.values().iterator();
-        for (int i = 0; i < RoomSQLiteQuery.DESIRED_POOL_SIZE; i++) {
-            assertThat(itr.next().mCapacity, is(i));
-        }
-    }
-}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/SharedSQLiteStatementTest.java b/room/runtime/src/test/java/android/arch/persistence/room/SharedSQLiteStatementTest.java
deleted file mode 100644
index 4e715e9..0000000
--- a/room/runtime/src/test/java/android/arch/persistence/room/SharedSQLiteStatementTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.persistence.room;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-@RunWith(JUnit4.class)
-public class SharedSQLiteStatementTest {
-    private SharedSQLiteStatement mSharedStmt;
-    RoomDatabase mDb;
-    @Before
-    public void init() {
-        mDb = mock(RoomDatabase.class);
-        when(mDb.compileStatement(anyString())).thenAnswer(new Answer<SupportSQLiteStatement>() {
-
-            @Override
-            public SupportSQLiteStatement answer(InvocationOnMock invocation) throws Throwable {
-                return mock(SupportSQLiteStatement.class);
-            }
-        });
-        when(mDb.getInvalidationTracker()).thenReturn(mock(InvalidationTracker.class));
-        mSharedStmt = new SharedSQLiteStatement(mDb) {
-            @Override
-            protected String createQuery() {
-                return "foo";
-            }
-        };
-    }
-
-    @Test
-    public void checkMainThread() {
-        mSharedStmt.acquire();
-        verify(mDb).assertNotMainThread();
-    }
-
-    @Test
-    public void basic() {
-        assertThat(mSharedStmt.acquire(), notNullValue());
-    }
-
-    @Test
-    public void getTwiceWithoutReleasing() {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt2, notNullValue());
-        assertThat(stmt1, is(not(stmt2)));
-    }
-
-    @Test
-    public void getTwiceWithReleasing() {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        mSharedStmt.release(stmt1);
-        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt1, is(stmt2));
-    }
-
-    @Test
-    public void getFromAnotherThreadWhileHolding() throws ExecutionException, InterruptedException {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
-                new Callable<SupportSQLiteStatement>() {
-                    @Override
-                    public SupportSQLiteStatement call() throws Exception {
-                        return mSharedStmt.acquire();
-                    }
-                });
-        new Thread(task).run();
-        SupportSQLiteStatement stmt2 = task.get();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt2, notNullValue());
-        assertThat(stmt1, is(not(stmt2)));
-    }
-
-    @Test
-    public void getFromAnotherThreadAfterReleasing() throws ExecutionException,
-            InterruptedException {
-        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
-        mSharedStmt.release(stmt1);
-        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
-                new Callable<SupportSQLiteStatement>() {
-                    @Override
-                    public SupportSQLiteStatement call() throws Exception {
-                        return mSharedStmt.acquire();
-                    }
-                });
-        new Thread(task).run();
-        SupportSQLiteStatement stmt2 = task.get();
-        assertThat(stmt1, notNullValue());
-        assertThat(stmt1, is(stmt2));
-    }
-}
diff --git a/room/runtime/src/test/java/android/arch/persistence/room/util/StringUtilTest.java b/room/runtime/src/test/java/android/arch/persistence/room/util/StringUtilTest.java
deleted file mode 100644
index c10fab6..0000000
--- a/room/runtime/src/test/java/android/arch/persistence/room/util/StringUtilTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.util;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import static java.util.Arrays.asList;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Collections;
-
-@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
-@RunWith(JUnit4.class)
-public class StringUtilTest {
-    @Test
-    public void testEmpty() {
-        assertThat(StringUtil.splitToIntList(""), is(Collections.<Integer>emptyList()));
-        assertThat(StringUtil.joinIntoString(Collections.<Integer>emptyList()), is(""));
-    }
-
-    @Test
-    public void testNull() {
-        assertThat(StringUtil.splitToIntList(null), nullValue());
-        assertThat(StringUtil.joinIntoString(null), nullValue());
-    }
-
-    @Test
-    public void testSingle() {
-        assertThat(StringUtil.splitToIntList("4"), is(asList(4)));
-        assertThat(StringUtil.joinIntoString(asList(4)), is("4"));
-    }
-
-    @Test
-    public void testMultiple() {
-        assertThat(StringUtil.splitToIntList("4,5"), is(asList(4, 5)));
-        assertThat(StringUtil.joinIntoString(asList(4, 5)), is("4,5"));
-    }
-
-    @Test
-    public void testNegative() {
-        assertThat(StringUtil.splitToIntList("-4,-5,6,-7"), is(asList(-4, -5, 6, -7)));
-        assertThat(StringUtil.joinIntoString(asList(-4, -5, 6, -7)), is("-4,-5,6,-7"));
-    }
-
-    @Test
-    public void ignoreMalformed() {
-        assertThat(StringUtil.splitToIntList("-4,a,5,7"), is(asList(-4, 5, 7)));
-    }
-}
diff --git a/room/runtime/src/test/java/androidx/room/BuilderTest.java b/room/runtime/src/test/java/androidx/room/BuilderTest.java
new file mode 100644
index 0000000..cd425cd
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/BuilderTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+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 static org.mockito.Mockito.mock;
+
+import static java.util.Arrays.asList;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@RunWith(JUnit4.class)
+public class BuilderTest {
+    @Test(expected = IllegalArgumentException.class)
+    public void nullContext() {
+        //noinspection ConstantConditions
+        Room.databaseBuilder(null, RoomDatabase.class, "bla").build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void nullContext2() {
+        //noinspection ConstantConditions
+        Room.inMemoryDatabaseBuilder(null, RoomDatabase.class).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void nullName() {
+        //noinspection ConstantConditions
+        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, null).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void emptyName() {
+        Room.databaseBuilder(mock(Context.class), RoomDatabase.class, "  ").build();
+    }
+
+    @Test
+    public void migration() {
+        Migration m1 = new EmptyMigration(0, 1);
+        Migration m2 = new EmptyMigration(1, 2);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1, m2).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(0, 1), is(asList(m1)));
+        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
+        assertThat(migrations.findMigrationPath(0, 2), is(asList(m1, m2)));
+        assertThat(migrations.findMigrationPath(2, 0), CoreMatchers.<List<Migration>>nullValue());
+        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
+    }
+
+    @Test
+    public void migrationOverride() {
+        Migration m1 = new EmptyMigration(0, 1);
+        Migration m2 = new EmptyMigration(1, 2);
+        Migration m3 = new EmptyMigration(0, 1);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1, m2, m3).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(0, 1), is(asList(m3)));
+        assertThat(migrations.findMigrationPath(1, 2), is(asList(m2)));
+        assertThat(migrations.findMigrationPath(0, 3), CoreMatchers.<List<Migration>>nullValue());
+    }
+
+    @Test
+    public void migrationJump() {
+        Migration m1 = new EmptyMigration(0, 1);
+        Migration m2 = new EmptyMigration(1, 2);
+        Migration m3 = new EmptyMigration(2, 3);
+        Migration m4 = new EmptyMigration(0, 3);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1, m2, m3, m4).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(0, 3), is(asList(m4)));
+        assertThat(migrations.findMigrationPath(1, 3), is(asList(m2, m3)));
+    }
+
+    @Test
+    public void migrationDowngrade() {
+        Migration m1_2 = new EmptyMigration(1, 2);
+        Migration m2_3 = new EmptyMigration(2, 3);
+        Migration m3_4 = new EmptyMigration(3, 4);
+        Migration m3_2 = new EmptyMigration(3, 2);
+        Migration m2_1 = new EmptyMigration(2, 1);
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .addMigrations(m1_2, m2_3, m3_4, m3_2, m2_1).build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        RoomDatabase.MigrationContainer migrations = config.migrationContainer;
+        assertThat(migrations.findMigrationPath(3, 2), is(asList(m3_2)));
+        assertThat(migrations.findMigrationPath(3, 1), is(asList(m3_2, m2_1)));
+    }
+
+    @Test
+    public void skipMigration() {
+        Context context = mock(Context.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .fallbackToDestructiveMigration()
+                .build();
+
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.requireMigration, is(false));
+    }
+
+    @Test
+    public void fallbackToDestructiveMigrationFrom_calledOnce_migrationsNotRequiredForValues() {
+        Context context = mock(Context.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .fallbackToDestructiveMigrationFrom(1, 2).build();
+
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.isMigrationRequiredFrom(1), is(false));
+        assertThat(config.isMigrationRequiredFrom(2), is(false));
+    }
+
+    @Test
+    public void fallbackToDestructiveMigrationFrom_calledTwice_migrationsNotRequiredForValues() {
+        Context context = mock(Context.class);
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .fallbackToDestructiveMigrationFrom(1, 2)
+                .fallbackToDestructiveMigrationFrom(3, 4)
+                .build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+
+        assertThat(config.isMigrationRequiredFrom(1), is(false));
+        assertThat(config.isMigrationRequiredFrom(2), is(false));
+        assertThat(config.isMigrationRequiredFrom(3), is(false));
+        assertThat(config.isMigrationRequiredFrom(4), is(false));
+    }
+
+    @Test
+    public void isMigrationRequiredFrom_fallBackToDestructiveCalled_alwaysReturnsFalse() {
+        Context context = mock(Context.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .fallbackToDestructiveMigration()
+                .build();
+
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.isMigrationRequiredFrom(0), is(false));
+        assertThat(config.isMigrationRequiredFrom(1), is(false));
+        assertThat(config.isMigrationRequiredFrom(5), is(false));
+        assertThat(config.isMigrationRequiredFrom(12), is(false));
+        assertThat(config.isMigrationRequiredFrom(132), is(false));
+    }
+
+    @Test
+    public void isMigrationRequiredFrom_byDefault_alwaysReturnsTrue() {
+        Context context = mock(Context.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .build();
+
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.isMigrationRequiredFrom(0), is(true));
+        assertThat(config.isMigrationRequiredFrom(1), is(true));
+        assertThat(config.isMigrationRequiredFrom(5), is(true));
+        assertThat(config.isMigrationRequiredFrom(12), is(true));
+        assertThat(config.isMigrationRequiredFrom(132), is(true));
+    }
+
+    @Test
+    public void isMigrationRequiredFrom_fallBackToDestFromCalled_falseForProvidedValues() {
+        Context context = mock(Context.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .fallbackToDestructiveMigrationFrom(1, 4, 81)
+                .build();
+
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.isMigrationRequiredFrom(1), is(false));
+        assertThat(config.isMigrationRequiredFrom(4), is(false));
+        assertThat(config.isMigrationRequiredFrom(81), is(false));
+    }
+
+    @Test
+    public void isMigrationRequiredFrom_fallBackToDestFromCalled_trueForNonProvidedValues() {
+        Context context = mock(Context.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .fallbackToDestructiveMigrationFrom(1, 4, 81)
+                .build();
+
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.isMigrationRequiredFrom(2), is(true));
+        assertThat(config.isMigrationRequiredFrom(3), is(true));
+        assertThat(config.isMigrationRequiredFrom(73), is(true));
+    }
+
+    @Test
+    public void createBasic() {
+        Context context = mock(Context.class);
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config, notNullValue());
+        assertThat(config.context, is(context));
+        assertThat(config.name, is(nullValue()));
+        assertThat(config.allowMainThreadQueries, is(false));
+        assertThat(config.journalMode, is(RoomDatabase.JournalMode.TRUNCATE));
+        assertThat(config.sqliteOpenHelperFactory,
+                instanceOf(FrameworkSQLiteOpenHelperFactory.class));
+    }
+
+    @Test
+    public void createAllowMainThread() {
+        Context context = mock(Context.class);
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .allowMainThreadQueries()
+                .build();
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.allowMainThreadQueries, is(true));
+    }
+
+    @Test
+    public void createWriteAheadLogging() {
+        Context context = mock(Context.class);
+        TestDatabase db = Room.databaseBuilder(context, TestDatabase.class, "foo")
+                .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING).build();
+        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config.journalMode, is(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING));
+    }
+
+    @Test
+    public void createWithFactoryAndVersion() {
+        Context context = mock(Context.class);
+        SupportSQLiteOpenHelper.Factory factory = mock(SupportSQLiteOpenHelper.Factory.class);
+
+        TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class)
+                .openHelperFactory(factory)
+                .build();
+        assertThat(db, instanceOf(BuilderTest_TestDatabase_Impl.class));
+        DatabaseConfiguration config = ((BuilderTest_TestDatabase_Impl) db).mConfig;
+        assertThat(config, notNullValue());
+        assertThat(config.sqliteOpenHelperFactory, is(factory));
+    }
+
+    abstract static class TestDatabase extends RoomDatabase {
+    }
+
+    static class EmptyMigration extends Migration {
+        EmptyMigration(int start, int end) {
+            super(start, end);
+        }
+
+        @Override
+        public void migrate(@NonNull SupportSQLiteDatabase database) {
+        }
+    }
+
+}
diff --git a/room/runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.java b/room/runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.java
new file mode 100644
index 0000000..d796bdc
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/BuilderTest_TestDatabase_Impl.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 androidx.room;
+
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+public class BuilderTest_TestDatabase_Impl extends BuilderTest.TestDatabase {
+    DatabaseConfiguration mConfig;
+    @Override
+    public void init(DatabaseConfiguration configuration) {
+        super.init(configuration);
+        mConfig = configuration;
+    }
+
+    @Override
+    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {
+        return null;
+    }
+
+    @Override
+    protected InvalidationTracker createInvalidationTracker() {
+        return null;
+    }
+
+    @Override
+    public void clearAllTables() {
+    }
+}
diff --git a/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java b/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
new file mode 100644
index 0000000..f4ffd24
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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 androidx.room;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsCollectionContaining.hasItem;
+import static org.hamcrest.core.IsCollectionContaining.hasItems;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+
+import androidx.annotation.NonNull;
+import androidx.arch.core.executor.JunitTaskExecutorRule;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+@RunWith(JUnit4.class)
+public class InvalidationTrackerTest {
+    private InvalidationTracker mTracker;
+    private RoomDatabase mRoomDatabase;
+    private SupportSQLiteOpenHelper mOpenHelper;
+    @Rule
+    public JunitTaskExecutorRule mTaskExecutorRule = new JunitTaskExecutorRule(1, true);
+
+    @Before
+    public void setup() {
+        mRoomDatabase = mock(RoomDatabase.class);
+        SupportSQLiteDatabase sqliteDb = mock(SupportSQLiteDatabase.class);
+        final SupportSQLiteStatement statement = mock(SupportSQLiteStatement.class);
+        mOpenHelper = mock(SupportSQLiteOpenHelper.class);
+
+        doReturn(statement).when(sqliteDb).compileStatement(eq(InvalidationTracker.CLEANUP_SQL));
+        doReturn(sqliteDb).when(mOpenHelper).getWritableDatabase();
+        doReturn(true).when(mRoomDatabase).isOpen();
+        ReentrantLock closeLock = new ReentrantLock();
+        doReturn(closeLock).when(mRoomDatabase).getCloseLock();
+        //noinspection ResultOfMethodCallIgnored
+        doReturn(mOpenHelper).when(mRoomDatabase).getOpenHelper();
+
+        mTracker = new InvalidationTracker(mRoomDatabase, "a", "B", "i");
+        mTracker.internalInit(sqliteDb);
+    }
+
+    @Before
+    public void setLocale() {
+        Locale.setDefault(Locale.forLanguageTag("tr-TR"));
+    }
+
+    @After
+    public void unsetLocale() {
+        Locale.setDefault(Locale.US);
+    }
+
+    @Test
+    public void tableIds() {
+        assertThat(mTracker.mTableIdLookup.get("a"), is(0));
+        assertThat(mTracker.mTableIdLookup.get("b"), is(1));
+    }
+
+    @Test
+    public void testWeak() throws InterruptedException {
+        final AtomicInteger data = new AtomicInteger(0);
+        InvalidationTracker.Observer observer = new InvalidationTracker.Observer("a") {
+            @Override
+            public void onInvalidated(@NonNull Set<String> tables) {
+                data.incrementAndGet();
+            }
+        };
+        mTracker.addWeakObserver(observer);
+        setVersions(1, 0);
+        refreshSync();
+        assertThat(data.get(), is(1));
+        observer = null;
+        forceGc();
+        setVersions(2, 0);
+        refreshSync();
+        assertThat(data.get(), is(1));
+    }
+
+    @Test
+    public void addRemoveObserver() throws Exception {
+        InvalidationTracker.Observer observer = new LatchObserver(1, "a");
+        mTracker.addObserver(observer);
+        assertThat(mTracker.mObserverMap.size(), is(1));
+        mTracker.removeObserver(new LatchObserver(1, "a"));
+        assertThat(mTracker.mObserverMap.size(), is(1));
+        mTracker.removeObserver(observer);
+        assertThat(mTracker.mObserverMap.size(), is(0));
+    }
+
+    private void drainTasks() throws InterruptedException {
+        mTaskExecutorRule.drainTasks(200);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void badObserver() {
+        InvalidationTracker.Observer observer = new LatchObserver(1, "x");
+        mTracker.addObserver(observer);
+    }
+
+    @Test
+    public void refreshReadValues() throws Exception {
+        setVersions(1, 0, 2, 1);
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{1, 2, 0}));
+
+        setVersions(3, 1);
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{1, 3, 0}));
+
+        setVersions(7, 0);
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{7, 3, 0}));
+
+        refreshSync();
+        assertThat(mTracker.mTableVersions, is(new long[]{7, 3, 0}));
+    }
+
+    private void refreshSync() throws InterruptedException {
+        mTracker.refreshVersionsAsync();
+        drainTasks();
+    }
+
+    @Test
+    public void refreshCheckTasks() throws Exception {
+        when(mRoomDatabase.query(anyString(), any(Object[].class)))
+                .thenReturn(mock(Cursor.class));
+        mTracker.refreshVersionsAsync();
+        mTracker.refreshVersionsAsync();
+        verify(mTaskExecutorRule.getTaskExecutor()).executeOnDiskIO(mTracker.mRefreshRunnable);
+        drainTasks();
+
+        reset(mTaskExecutorRule.getTaskExecutor());
+        mTracker.refreshVersionsAsync();
+        verify(mTaskExecutorRule.getTaskExecutor()).executeOnDiskIO(mTracker.mRefreshRunnable);
+    }
+
+    @Test
+    public void observe1Table() throws Exception {
+        LatchObserver observer = new LatchObserver(1, "a");
+        mTracker.addObserver(observer);
+        setVersions(1, 0, 2, 1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("a"));
+
+        setVersions(3, 1);
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(false));
+
+        setVersions(4, 0);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("a"));
+    }
+
+    @Test
+    public void observe2Tables() throws Exception {
+        LatchObserver observer = new LatchObserver(1, "A", "B");
+        mTracker.addObserver(observer);
+        setVersions(1, 0, 2, 1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(2));
+        assertThat(observer.getInvalidatedTables(), hasItems("A", "B"));
+
+        setVersions(3, 1);
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("B"));
+
+        setVersions(4, 0);
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(true));
+        assertThat(observer.getInvalidatedTables().size(), is(1));
+        assertThat(observer.getInvalidatedTables(), hasItem("A"));
+
+        observer.reset(1);
+        refreshSync();
+        assertThat(observer.await(), is(false));
+    }
+
+    @Test
+    public void locale() {
+        LatchObserver observer = new LatchObserver(1, "I");
+        mTracker.addObserver(observer);
+    }
+
+    @Test
+    public void closedDb() {
+        doReturn(false).when(mRoomDatabase).isOpen();
+        doThrow(new IllegalStateException("foo")).when(mOpenHelper).getWritableDatabase();
+        mTracker.addObserver(new LatchObserver(1, "a", "b"));
+        mTracker.mRefreshRunnable.run();
+    }
+
+    // @Test - disabled due to flakiness b/65257997
+    public void closedDbAfterOpen() throws InterruptedException {
+        setVersions(3, 1);
+        mTracker.addObserver(new LatchObserver(1, "a", "b"));
+        mTracker.syncTriggers();
+        mTracker.mRefreshRunnable.run();
+        doThrow(new SQLiteException("foo")).when(mRoomDatabase).query(
+                Mockito.eq(InvalidationTracker.SELECT_UPDATED_TABLES_SQL),
+                any(Object[].class));
+        mTracker.mPendingRefresh.set(true);
+        mTracker.mRefreshRunnable.run();
+    }
+
+    /**
+     * Key value pairs of VERSION, TABLE_ID
+     */
+    private void setVersions(int... keyValuePairs) throws InterruptedException {
+        // mockito does not like multi-threaded access so before setting versions, make sure we
+        // sync background tasks.
+        drainTasks();
+        Cursor cursor = createCursorWithValues(keyValuePairs);
+        doReturn(cursor).when(mRoomDatabase).query(
+                Mockito.eq(InvalidationTracker.SELECT_UPDATED_TABLES_SQL),
+                any(Object[].class)
+        );
+    }
+
+    private Cursor createCursorWithValues(final int... keyValuePairs) {
+        Cursor cursor = mock(Cursor.class);
+        final AtomicInteger index = new AtomicInteger(-2);
+        when(cursor.moveToNext()).thenAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                return index.addAndGet(2) < keyValuePairs.length;
+            }
+        });
+        Answer<Integer> intAnswer = new Answer<Integer>() {
+            @Override
+            public Integer answer(InvocationOnMock invocation) throws Throwable {
+                return keyValuePairs[index.intValue() + (Integer) invocation.getArguments()[0]];
+            }
+        };
+        Answer<Long> longAnswer = new Answer<Long>() {
+            @Override
+            public Long answer(InvocationOnMock invocation) throws Throwable {
+                return (long) keyValuePairs[index.intValue()
+                        + (Integer) invocation.getArguments()[0]];
+            }
+        };
+        when(cursor.getInt(anyInt())).thenAnswer(intAnswer);
+        when(cursor.getLong(anyInt())).thenAnswer(longAnswer);
+        return cursor;
+    }
+
+    static class LatchObserver extends InvalidationTracker.Observer {
+        private CountDownLatch mLatch;
+        private Set<String> mInvalidatedTables;
+
+        LatchObserver(int count, String... tableNames) {
+            super(tableNames);
+            mLatch = new CountDownLatch(count);
+        }
+
+        boolean await() throws InterruptedException {
+            return mLatch.await(3, TimeUnit.SECONDS);
+        }
+
+        @Override
+        public void onInvalidated(@NonNull Set<String> tables) {
+            mInvalidatedTables = tables;
+            mLatch.countDown();
+        }
+
+        void reset(@SuppressWarnings("SameParameterValue") int count) {
+            mInvalidatedTables = null;
+            mLatch = new CountDownLatch(count);
+        }
+
+        Set<String> getInvalidatedTables() {
+            return mInvalidatedTables;
+        }
+    }
+
+    private static void forceGc() {
+        // Use a random index in the list to detect the garbage collection each time because
+        // .get() may accidentally trigger a strong reference during collection.
+        ArrayList<WeakReference<byte[]>> leak = new ArrayList<>();
+        do {
+            WeakReference<byte[]> arr = new WeakReference<>(new byte[100]);
+            leak.add(arr);
+        } while (leak.get((int) (Math.random() * leak.size())).get() != null);
+    }
+}
diff --git a/room/runtime/src/test/java/androidx/room/ObservedTableTrackerTest.java b/room/runtime/src/test/java/androidx/room/ObservedTableTrackerTest.java
new file mode 100644
index 0000000..224815f
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/ObservedTableTrackerTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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 androidx.room;
+
+
+import static androidx.room.InvalidationTracker.ObservedTableTracker.ADD;
+import static androidx.room.InvalidationTracker.ObservedTableTracker.NO_OP;
+import static androidx.room.InvalidationTracker.ObservedTableTracker.REMOVE;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class ObservedTableTrackerTest {
+    private static final int TABLE_COUNT = 5;
+    private InvalidationTracker.ObservedTableTracker mTracker;
+
+    @Before
+    public void setup() {
+        mTracker = new InvalidationTracker.ObservedTableTracker(TABLE_COUNT);
+    }
+
+    @Test
+    public void basicAdd() {
+        mTracker.onAdded(2, 3);
+        assertThat(mTracker.getTablesToSync(), is(createResponse(2, ADD, 3, ADD)));
+    }
+
+    @Test
+    public void basicRemove() {
+        initState(2, 3);
+        mTracker.onRemoved(3);
+        assertThat(mTracker.getTablesToSync(), is(createResponse(3, REMOVE)));
+    }
+
+    @Test
+    public void noChange() {
+        initState(1, 3);
+        mTracker.onAdded(3);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+    }
+
+    @Test
+    public void returnNullUntilSync() {
+        initState(1, 3);
+        mTracker.onAdded(4);
+        assertThat(mTracker.getTablesToSync(), is(createResponse(4, ADD)));
+        mTracker.onAdded(0);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onSyncCompleted();
+        assertThat(mTracker.getTablesToSync(), is(createResponse(0, ADD)));
+    }
+
+    @Test
+    public void multipleAdditionsDeletions() {
+        initState(2, 4);
+        mTracker.onAdded(2);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onAdded(2, 4);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onRemoved(2);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onRemoved(2, 4);
+        assertThat(mTracker.getTablesToSync(), is(nullValue()));
+        mTracker.onAdded(1, 3);
+        mTracker.onRemoved(2, 4);
+        assertThat(mTracker.getTablesToSync(), is(
+                createResponse(1, ADD, 2, REMOVE, 3, ADD, 4, REMOVE)));
+    }
+
+    private void initState(int... tableIds) {
+        mTracker.onAdded(tableIds);
+        mTracker.getTablesToSync();
+        mTracker.onSyncCompleted();
+    }
+
+    private static int[] createResponse(int... tuples) {
+        int[] result = new int[TABLE_COUNT];
+        Arrays.fill(result, NO_OP);
+        for (int i = 0; i < tuples.length; i += 2) {
+            result[tuples[i]] = tuples[i + 1];
+        }
+        return result;
+    }
+}
diff --git a/room/runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.java b/room/runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.java
new file mode 100644
index 0000000..9a70c2e
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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 androidx.room;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import androidx.sqlite.db.SupportSQLiteProgram;
+
+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.Iterator;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class RoomSQLiteQueryTest {
+    @Before
+    public void clear() {
+        RoomSQLiteQuery.sQueryPool.clear();
+    }
+
+    @Test
+    public void acquireBasic() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        assertThat(query.getSql(), is("abc"));
+        assertThat(query.mArgCount, is(3));
+        assertThat(query.mBlobBindings.length, is(4));
+        assertThat(query.mLongBindings.length, is(4));
+        assertThat(query.mStringBindings.length, is(4));
+        assertThat(query.mDoubleBindings.length, is(4));
+    }
+
+    @Test
+    public void acquireSameSizeAgain() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        query.release();
+        assertThat(RoomSQLiteQuery.acquire("blah", 3), sameInstance(query));
+    }
+
+    @Test
+    public void acquireSameSizeWithoutRelease() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        assertThat(RoomSQLiteQuery.acquire("fda", 3), not(sameInstance(query)));
+    }
+
+    @Test
+    public void bindings() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 6);
+        byte[] myBlob = new byte[3];
+        long myLong = 3L;
+        double myDouble = 7.0;
+        String myString = "ss";
+        query.bindBlob(1, myBlob);
+        query.bindLong(2, myLong);
+        query.bindNull(3);
+        query.bindDouble(4, myDouble);
+        query.bindString(5, myString);
+        query.bindNull(6);
+        SupportSQLiteProgram program = mock(SupportSQLiteProgram.class);
+        query.bindTo(program);
+
+        verify(program).bindBlob(1, myBlob);
+        verify(program).bindLong(2, myLong);
+        verify(program).bindNull(3);
+        verify(program).bindDouble(4, myDouble);
+        verify(program).bindString(5, myString);
+        verify(program).bindNull(6);
+    }
+
+    @Test
+    public void dontKeepSameSizeTwice() {
+        RoomSQLiteQuery query1 = RoomSQLiteQuery.acquire("abc", 3);
+        RoomSQLiteQuery query2 = RoomSQLiteQuery.acquire("zx", 3);
+        RoomSQLiteQuery query3 = RoomSQLiteQuery.acquire("qw", 0);
+
+        query1.release();
+        query2.release();
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(1));
+
+        query3.release();
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(2));
+    }
+
+    @Test
+    public void returnExistingForSmallerSize() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        query.release();
+        assertThat(RoomSQLiteQuery.acquire("dsa", 2), sameInstance(query));
+    }
+
+    @Test
+    public void returnNewForBigger() {
+        RoomSQLiteQuery query = RoomSQLiteQuery.acquire("abc", 3);
+        query.release();
+        assertThat(RoomSQLiteQuery.acquire("dsa", 4), not(sameInstance(query)));
+    }
+
+    @Test
+    public void pruneCache() {
+        for (int i = 0; i < RoomSQLiteQuery.POOL_LIMIT; i++) {
+            RoomSQLiteQuery.acquire("dsdsa", i).release();
+        }
+        pruneCacheTest();
+    }
+
+    @Test
+    public void pruneCacheReverseInsertion() {
+        List<RoomSQLiteQuery> queries = new ArrayList<>();
+        for (int i = RoomSQLiteQuery.POOL_LIMIT - 1; i >= 0; i--) {
+            queries.add(RoomSQLiteQuery.acquire("dsdsa", i));
+        }
+        for (RoomSQLiteQuery query : queries) {
+            query.release();
+        }
+        pruneCacheTest();
+    }
+
+    private void pruneCacheTest() {
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(RoomSQLiteQuery.POOL_LIMIT));
+        RoomSQLiteQuery.acquire("dsadsa", RoomSQLiteQuery.POOL_LIMIT + 1).release();
+        assertThat(RoomSQLiteQuery.sQueryPool.size(), is(RoomSQLiteQuery.DESIRED_POOL_SIZE));
+        Iterator<RoomSQLiteQuery> itr = RoomSQLiteQuery.sQueryPool.values().iterator();
+        for (int i = 0; i < RoomSQLiteQuery.DESIRED_POOL_SIZE; i++) {
+            assertThat(itr.next().mCapacity, is(i));
+        }
+    }
+}
diff --git a/room/runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.java b/room/runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.java
new file mode 100644
index 0000000..4dad7c2
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.sqlite.db.SupportSQLiteStatement;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+@RunWith(JUnit4.class)
+public class SharedSQLiteStatementTest {
+    private SharedSQLiteStatement mSharedStmt;
+    RoomDatabase mDb;
+    @Before
+    public void init() {
+        mDb = mock(RoomDatabase.class);
+        when(mDb.compileStatement(anyString())).thenAnswer(new Answer<SupportSQLiteStatement>() {
+
+            @Override
+            public SupportSQLiteStatement answer(InvocationOnMock invocation) throws Throwable {
+                return mock(SupportSQLiteStatement.class);
+            }
+        });
+        when(mDb.getInvalidationTracker()).thenReturn(mock(InvalidationTracker.class));
+        mSharedStmt = new SharedSQLiteStatement(mDb) {
+            @Override
+            protected String createQuery() {
+                return "foo";
+            }
+        };
+    }
+
+    @Test
+    public void checkMainThread() {
+        mSharedStmt.acquire();
+        verify(mDb).assertNotMainThread();
+    }
+
+    @Test
+    public void basic() {
+        assertThat(mSharedStmt.acquire(), notNullValue());
+    }
+
+    @Test
+    public void getTwiceWithoutReleasing() {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt2, notNullValue());
+        assertThat(stmt1, is(not(stmt2)));
+    }
+
+    @Test
+    public void getTwiceWithReleasing() {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        mSharedStmt.release(stmt1);
+        SupportSQLiteStatement stmt2 = mSharedStmt.acquire();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt1, is(stmt2));
+    }
+
+    @Test
+    public void getFromAnotherThreadWhileHolding() throws ExecutionException, InterruptedException {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
+                new Callable<SupportSQLiteStatement>() {
+                    @Override
+                    public SupportSQLiteStatement call() throws Exception {
+                        return mSharedStmt.acquire();
+                    }
+                });
+        new Thread(task).run();
+        SupportSQLiteStatement stmt2 = task.get();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt2, notNullValue());
+        assertThat(stmt1, is(not(stmt2)));
+    }
+
+    @Test
+    public void getFromAnotherThreadAfterReleasing() throws ExecutionException,
+            InterruptedException {
+        SupportSQLiteStatement stmt1 = mSharedStmt.acquire();
+        mSharedStmt.release(stmt1);
+        FutureTask<SupportSQLiteStatement> task = new FutureTask<>(
+                new Callable<SupportSQLiteStatement>() {
+                    @Override
+                    public SupportSQLiteStatement call() throws Exception {
+                        return mSharedStmt.acquire();
+                    }
+                });
+        new Thread(task).run();
+        SupportSQLiteStatement stmt2 = task.get();
+        assertThat(stmt1, notNullValue());
+        assertThat(stmt1, is(stmt2));
+    }
+}
diff --git a/room/runtime/src/test/java/androidx/room/util/StringUtilTest.java b/room/runtime/src/test/java/androidx/room/util/StringUtilTest.java
new file mode 100644
index 0000000..7ea316c
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/util/StringUtilTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.util;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import static java.util.Arrays.asList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
+@RunWith(JUnit4.class)
+public class StringUtilTest {
+    @Test
+    public void testEmpty() {
+        assertThat(StringUtil.splitToIntList(""), is(Collections.<Integer>emptyList()));
+        assertThat(StringUtil.joinIntoString(Collections.<Integer>emptyList()), is(""));
+    }
+
+    @Test
+    public void testNull() {
+        assertThat(StringUtil.splitToIntList(null), nullValue());
+        assertThat(StringUtil.joinIntoString(null), nullValue());
+    }
+
+    @Test
+    public void testSingle() {
+        assertThat(StringUtil.splitToIntList("4"), is(asList(4)));
+        assertThat(StringUtil.joinIntoString(asList(4)), is("4"));
+    }
+
+    @Test
+    public void testMultiple() {
+        assertThat(StringUtil.splitToIntList("4,5"), is(asList(4, 5)));
+        assertThat(StringUtil.joinIntoString(asList(4, 5)), is("4,5"));
+    }
+
+    @Test
+    public void testNegative() {
+        assertThat(StringUtil.splitToIntList("-4,-5,6,-7"), is(asList(-4, -5, 6, -7)));
+        assertThat(StringUtil.joinIntoString(asList(-4, -5, 6, -7)), is("-4,-5,6,-7"));
+    }
+
+    @Test
+    public void ignoreMalformed() {
+        assertThat(StringUtil.splitToIntList("-4,a,5,7"), is(asList(-4, 5, 7)));
+    }
+}
diff --git a/room/rxjava2/api/current.txt b/room/rxjava2/api/current.txt
index a7cffd3..be690d9 100644
--- a/room/rxjava2/api/current.txt
+++ b/room/rxjava2/api/current.txt
@@ -1,4 +1,4 @@
-package android.arch.persistence.room {
+package androidx.room {
 
   public class EmptyResultSetException extends java.lang.RuntimeException {
     ctor public EmptyResultSetException(java.lang.String);
@@ -6,7 +6,7 @@
 
   public class RxRoom {
     ctor public RxRoom();
-    method public static io.reactivex.Flowable<java.lang.Object> createFlowable(android.arch.persistence.room.RoomDatabase, java.lang.String...);
+    method public static io.reactivex.Flowable<java.lang.Object> createFlowable(androidx.room.RoomDatabase, java.lang.String...);
     field public static final java.lang.Object NOTHING;
   }
 
diff --git a/room/rxjava2/api/1.0.0.txt b/room/rxjava2/api_legacy/1.0.0.txt
similarity index 100%
rename from room/rxjava2/api/1.0.0.txt
rename to room/rxjava2/api_legacy/1.0.0.txt
diff --git a/room/rxjava2/api/1.0.0.txt b/room/rxjava2/api_legacy/1.1.0.txt
similarity index 100%
copy from room/rxjava2/api/1.0.0.txt
copy to room/rxjava2/api_legacy/1.1.0.txt
diff --git a/room/rxjava2/api/1.0.0.txt b/room/rxjava2/api_legacy/current.txt
similarity index 100%
copy from room/rxjava2/api/1.0.0.txt
copy to room/rxjava2/api_legacy/current.txt
diff --git a/room/rxjava2/build.gradle b/room/rxjava2/build.gradle
index 8dcaa6b..1457f79 100644
--- a/room/rxjava2/build.gradle
+++ b/room/rxjava2/build.gradle
@@ -24,9 +24,9 @@
 }
 
 dependencies {
-    api(project(":room:common"))
-    api(project(":room:runtime"))
-    api(project(":arch:runtime"))
+    api(project(":room:room-common"))
+    api(project(":room:room-runtime"))
+    api(project(":arch:core-runtime"))
     api(SUPPORT_CORE_UTILS, libs.support_exclude_config)
     api(RX_JAVA)
     testImplementation(JUNIT)
diff --git a/room/rxjava2/src/main/AndroidManifest.xml b/room/rxjava2/src/main/AndroidManifest.xml
index 33279c6..dc3a472 100644
--- a/room/rxjava2/src/main/AndroidManifest.xml
+++ b/room/rxjava2/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.persistence.room.rxjava2">
+          package="androidx.room.rxjava2">
 </manifest>
diff --git a/room/rxjava2/src/main/java/android/arch/persistence/room/EmptyResultSetException.java b/room/rxjava2/src/main/java/android/arch/persistence/room/EmptyResultSetException.java
deleted file mode 100644
index 0f2d281..0000000
--- a/room/rxjava2/src/main/java/android/arch/persistence/room/EmptyResultSetException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-/**
- * Thrown by Room when the query needs to return a result (e.g. in a Single&lt;T> query) but the
- * returned result set from the database is empty.
- */
-public class EmptyResultSetException extends RuntimeException {
-    /**
-     * Constructs a new EmptyResultSetException with the exception.
-     * @param message The SQL query which didn't return any results.
-     */
-    public EmptyResultSetException(String message) {
-        super(message);
-    }
-}
diff --git a/room/rxjava2/src/main/java/android/arch/persistence/room/RxRoom.java b/room/rxjava2/src/main/java/android/arch/persistence/room/RxRoom.java
deleted file mode 100644
index 285b3f8..0000000
--- a/room/rxjava2/src/main/java/android/arch/persistence/room/RxRoom.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import io.reactivex.BackpressureStrategy;
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-import io.reactivex.FlowableOnSubscribe;
-import io.reactivex.Scheduler;
-import io.reactivex.annotations.NonNull;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.disposables.Disposables;
-import io.reactivex.functions.Action;
-import io.reactivex.functions.Function;
-import io.reactivex.functions.Predicate;
-
-/**
- * Helper class to add RxJava2 support to Room.
- */
-@SuppressWarnings("WeakerAccess")
-public class RxRoom {
-    /**
-     * Data dispatched by the publisher created by {@link #createFlowable(RoomDatabase, String...)}.
-     */
-    public static final Object NOTHING = new Object();
-
-    /**
-     * Creates a {@link Flowable} that emits at least once and also re-emits whenever one of the
-     * observed tables is updated.
-     * <p>
-     * You can easily chain a database operation to downstream of this {@link Flowable} to ensure
-     * that it re-runs when database is modified.
-     * <p>
-     * Since database invalidation is batched, multiple changes in the database may results in just
-     * 1 emission.
-     *
-     * @param database   The database instance
-     * @param tableNames The list of table names that should be observed
-     * @return A {@link Flowable} which emits {@link #NOTHING} when one of the observed tables
-     * is modified (also once when the invalidation tracker connection is established).
-     */
-    public static Flowable<Object> createFlowable(final RoomDatabase database,
-            final String... tableNames) {
-        return Flowable.create(new FlowableOnSubscribe<Object>() {
-            @Override
-            public void subscribe(final FlowableEmitter<Object> emitter) throws Exception {
-                final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
-                        tableNames) {
-                    @Override
-                    public void onInvalidated(
-                            @android.support.annotation.NonNull Set<String> tables) {
-                        if (!emitter.isCancelled()) {
-                            emitter.onNext(NOTHING);
-                        }
-                    }
-                };
-                if (!emitter.isCancelled()) {
-                    database.getInvalidationTracker().addObserver(observer);
-                    emitter.setDisposable(Disposables.fromAction(new Action() {
-                        @Override
-                        public void run() throws Exception {
-                            database.getInvalidationTracker().removeObserver(observer);
-                        }
-                    }));
-                }
-
-                // emit once to avoid missing any data and also easy chaining
-                if (!emitter.isCancelled()) {
-                    emitter.onNext(NOTHING);
-                }
-            }
-        }, BackpressureStrategy.LATEST);
-    }
-
-    /**
-     * Helper method used by generated code to bind a Callable such that it will be run in
-     * our disk io thread and will automatically block null values since RxJava2 does not like null.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
-            final String[] tableNames, final Callable<T> callable) {
-        return createFlowable(database, tableNames).observeOn(sAppToolkitIOScheduler)
-                .map(new Function<Object, Optional<T>>() {
-                    @Override
-                    public Optional<T> apply(@NonNull Object o) throws Exception {
-                        T data = callable.call();
-                        return new Optional<>(data);
-                    }
-                }).filter(new Predicate<Optional<T>>() {
-                    @Override
-                    public boolean test(@NonNull Optional<T> optional) throws Exception {
-                        return optional.mValue != null;
-                    }
-                }).map(new Function<Optional<T>, T>() {
-                    @Override
-                    public T apply(@NonNull Optional<T> optional) throws Exception {
-                        return optional.mValue;
-                    }
-                });
-    }
-
-    private static Scheduler sAppToolkitIOScheduler = new Scheduler() {
-        @Override
-        public Worker createWorker() {
-            final AtomicBoolean mDisposed = new AtomicBoolean(false);
-            return new Worker() {
-                @Override
-                public Disposable schedule(@NonNull Runnable run, long delay,
-                        @NonNull TimeUnit unit) {
-                    DisposableRunnable disposable = new DisposableRunnable(run, mDisposed);
-                    ArchTaskExecutor.getInstance().executeOnDiskIO(run);
-                    return disposable;
-                }
-
-                @Override
-                public void dispose() {
-                    mDisposed.set(true);
-                }
-
-                @Override
-                public boolean isDisposed() {
-                    return mDisposed.get();
-                }
-            };
-        }
-    };
-
-    private static class DisposableRunnable implements Disposable, Runnable {
-        private final Runnable mActual;
-        private volatile boolean mDisposed = false;
-        private final AtomicBoolean mGlobalDisposed;
-
-        DisposableRunnable(Runnable actual, AtomicBoolean globalDisposed) {
-            mActual = actual;
-            mGlobalDisposed = globalDisposed;
-        }
-
-        @Override
-        public void dispose() {
-            mDisposed = true;
-        }
-
-        @Override
-        public boolean isDisposed() {
-            return mDisposed || mGlobalDisposed.get();
-        }
-
-        @Override
-        public void run() {
-            if (!isDisposed()) {
-                mActual.run();
-            }
-        }
-    }
-
-    static class Optional<T> {
-        @Nullable
-        final T mValue;
-
-        Optional(@Nullable T value) {
-            this.mValue = value;
-        }
-    }
-}
diff --git a/room/rxjava2/src/main/java/androidx/room/EmptyResultSetException.java b/room/rxjava2/src/main/java/androidx/room/EmptyResultSetException.java
new file mode 100644
index 0000000..3081842
--- /dev/null
+++ b/room/rxjava2/src/main/java/androidx/room/EmptyResultSetException.java
@@ -0,0 +1,31 @@
+/*
+ * 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 androidx.room;
+
+/**
+ * Thrown by Room when the query needs to return a result (e.g. in a Single&lt;T> query) but the
+ * returned result set from the database is empty.
+ */
+public class EmptyResultSetException extends RuntimeException {
+    /**
+     * Constructs a new EmptyResultSetException with the exception.
+     * @param message The SQL query which didn't return any results.
+     */
+    public EmptyResultSetException(String message) {
+        super(message);
+    }
+}
diff --git a/room/rxjava2/src/main/java/androidx/room/RxRoom.java b/room/rxjava2/src/main/java/androidx/room/RxRoom.java
new file mode 100644
index 0000000..d2ba8bf
--- /dev/null
+++ b/room/rxjava2/src/main/java/androidx/room/RxRoom.java
@@ -0,0 +1,188 @@
+/*
+ * 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 androidx.room;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.arch.core.executor.ArchTaskExecutor;
+
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import io.reactivex.FlowableEmitter;
+import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.Scheduler;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.disposables.Disposables;
+import io.reactivex.functions.Action;
+import io.reactivex.functions.Function;
+import io.reactivex.functions.Predicate;
+
+/**
+ * Helper class to add RxJava2 support to Room.
+ */
+@SuppressWarnings("WeakerAccess")
+public class RxRoom {
+    /**
+     * Data dispatched by the publisher created by {@link #createFlowable(RoomDatabase, String...)}.
+     */
+    public static final Object NOTHING = new Object();
+
+    /**
+     * Creates a {@link Flowable} that emits at least once and also re-emits whenever one of the
+     * observed tables is updated.
+     * <p>
+     * You can easily chain a database operation to downstream of this {@link Flowable} to ensure
+     * that it re-runs when database is modified.
+     * <p>
+     * Since database invalidation is batched, multiple changes in the database may results in just
+     * 1 emission.
+     *
+     * @param database   The database instance
+     * @param tableNames The list of table names that should be observed
+     * @return A {@link Flowable} which emits {@link #NOTHING} when one of the observed tables
+     * is modified (also once when the invalidation tracker connection is established).
+     */
+    public static Flowable<Object> createFlowable(final RoomDatabase database,
+            final String... tableNames) {
+        return Flowable.create(new FlowableOnSubscribe<Object>() {
+            @Override
+            public void subscribe(final FlowableEmitter<Object> emitter) throws Exception {
+                final InvalidationTracker.Observer observer = new InvalidationTracker.Observer(
+                        tableNames) {
+                    @Override
+                    public void onInvalidated(@androidx.annotation.NonNull Set<String> tables) {
+                        if (!emitter.isCancelled()) {
+                            emitter.onNext(NOTHING);
+                        }
+                    }
+                };
+                if (!emitter.isCancelled()) {
+                    database.getInvalidationTracker().addObserver(observer);
+                    emitter.setDisposable(Disposables.fromAction(new Action() {
+                        @Override
+                        public void run() throws Exception {
+                            database.getInvalidationTracker().removeObserver(observer);
+                        }
+                    }));
+                }
+
+                // emit once to avoid missing any data and also easy chaining
+                if (!emitter.isCancelled()) {
+                    emitter.onNext(NOTHING);
+                }
+            }
+        }, BackpressureStrategy.LATEST);
+    }
+
+    /**
+     * Helper method used by generated code to bind a Callable such that it will be run in
+     * our disk io thread and will automatically block null values since RxJava2 does not like null.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
+            final String[] tableNames, final Callable<T> callable) {
+        return createFlowable(database, tableNames).observeOn(sAppToolkitIOScheduler)
+                .map(new Function<Object, Optional<T>>() {
+                    @Override
+                    public Optional<T> apply(@NonNull Object o) throws Exception {
+                        T data = callable.call();
+                        return new Optional<>(data);
+                    }
+                }).filter(new Predicate<Optional<T>>() {
+                    @Override
+                    public boolean test(@NonNull Optional<T> optional) throws Exception {
+                        return optional.mValue != null;
+                    }
+                }).map(new Function<Optional<T>, T>() {
+                    @Override
+                    public T apply(@NonNull Optional<T> optional) throws Exception {
+                        return optional.mValue;
+                    }
+                });
+    }
+
+    private static Scheduler sAppToolkitIOScheduler = new Scheduler() {
+        @Override
+        public Worker createWorker() {
+            final AtomicBoolean mDisposed = new AtomicBoolean(false);
+            return new Worker() {
+                @Override
+                public Disposable schedule(@NonNull Runnable run, long delay,
+                        @NonNull TimeUnit unit) {
+                    DisposableRunnable disposable = new DisposableRunnable(run, mDisposed);
+                    ArchTaskExecutor.getInstance().executeOnDiskIO(run);
+                    return disposable;
+                }
+
+                @Override
+                public void dispose() {
+                    mDisposed.set(true);
+                }
+
+                @Override
+                public boolean isDisposed() {
+                    return mDisposed.get();
+                }
+            };
+        }
+    };
+
+    private static class DisposableRunnable implements Disposable, Runnable {
+        private final Runnable mActual;
+        private volatile boolean mDisposed = false;
+        private final AtomicBoolean mGlobalDisposed;
+
+        DisposableRunnable(Runnable actual, AtomicBoolean globalDisposed) {
+            mActual = actual;
+            mGlobalDisposed = globalDisposed;
+        }
+
+        @Override
+        public void dispose() {
+            mDisposed = true;
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return mDisposed || mGlobalDisposed.get();
+        }
+
+        @Override
+        public void run() {
+            if (!isDisposed()) {
+                mActual.run();
+            }
+        }
+    }
+
+    static class Optional<T> {
+        @Nullable
+        final T mValue;
+
+        Optional(@Nullable T value) {
+            this.mValue = value;
+        }
+    }
+}
diff --git a/room/rxjava2/src/test/java/android/arch/persistence/room/RxRoomTest.java b/room/rxjava2/src/test/java/android/arch/persistence/room/RxRoomTest.java
deleted file mode 100644
index 502eaa1..0000000
--- a/room/rxjava2/src/test/java/android/arch/persistence/room/RxRoomTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.arch.core.executor.JunitTaskExecutorRule;
-
-import org.hamcrest.CoreMatchers;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicReference;
-
-import io.reactivex.Flowable;
-import io.reactivex.annotations.NonNull;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.functions.Consumer;
-import io.reactivex.subscribers.TestSubscriber;
-
-@RunWith(JUnit4.class)
-public class RxRoomTest {
-    @Rule
-    public JunitTaskExecutorRule mExecutor = new JunitTaskExecutorRule(1, false);
-    private RoomDatabase mDatabase;
-    private InvalidationTracker mInvalidationTracker;
-    private List<InvalidationTracker.Observer> mAddedObservers = new ArrayList<>();
-
-    @Before
-    public void init() {
-        mDatabase = mock(RoomDatabase.class);
-        mInvalidationTracker = mock(InvalidationTracker.class);
-        when(mDatabase.getInvalidationTracker()).thenReturn(mInvalidationTracker);
-        doAnswer(new Answer() {
-            @Override
-            public Object answer(InvocationOnMock invocation) throws Throwable {
-                mAddedObservers.add((InvalidationTracker.Observer) invocation.getArguments()[0]);
-                return null;
-            }
-        }).when(mInvalidationTracker).addObserver(any(InvalidationTracker.Observer.class));
-    }
-
-    @Test
-    public void basicAddRemove() {
-        Flowable<Object> flowable = RxRoom.createFlowable(mDatabase, "a", "b");
-        verify(mInvalidationTracker, never()).addObserver(any(InvalidationTracker.Observer.class));
-        Disposable disposable = flowable.subscribe();
-        verify(mInvalidationTracker).addObserver(any(InvalidationTracker.Observer.class));
-        assertThat(mAddedObservers.size(), CoreMatchers.is(1));
-
-        InvalidationTracker.Observer observer = mAddedObservers.get(0);
-        disposable.dispose();
-
-        verify(mInvalidationTracker).removeObserver(observer);
-
-        disposable = flowable.subscribe();
-        verify(mInvalidationTracker, times(2))
-                .addObserver(any(InvalidationTracker.Observer.class));
-        assertThat(mAddedObservers.size(), CoreMatchers.is(2));
-        assertThat(mAddedObservers.get(1), CoreMatchers.not(CoreMatchers.sameInstance(observer)));
-        InvalidationTracker.Observer observer2 = mAddedObservers.get(1);
-        disposable.dispose();
-        verify(mInvalidationTracker).removeObserver(observer2);
-    }
-
-    @Test
-    public void basicNotify() throws InterruptedException {
-        String[] tables = {"a", "b"};
-        Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
-        Flowable<Object> flowable = RxRoom.createFlowable(mDatabase, tables);
-        CountingConsumer consumer = new CountingConsumer();
-        Disposable disposable = flowable.subscribe(consumer);
-        assertThat(mAddedObservers.size(), CoreMatchers.is(1));
-        InvalidationTracker.Observer observer = mAddedObservers.get(0);
-        assertThat(consumer.mCount, CoreMatchers.is(1));
-        observer.onInvalidated(tableSet);
-        assertThat(consumer.mCount, CoreMatchers.is(2));
-        observer.onInvalidated(tableSet);
-        assertThat(consumer.mCount, CoreMatchers.is(3));
-        disposable.dispose();
-        observer.onInvalidated(tableSet);
-        assertThat(consumer.mCount, CoreMatchers.is(3));
-    }
-
-    @Test
-    public void internalCallable() throws InterruptedException {
-        final AtomicReference<String> value = new AtomicReference<>(null);
-        String[] tables = {"a", "b"};
-        Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
-        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, tables,
-                new Callable<String>() {
-                    @Override
-                    public String call() throws Exception {
-                        return value.get();
-                    }
-                });
-        final CountingConsumer consumer = new CountingConsumer();
-        flowable.subscribe(consumer);
-        InvalidationTracker.Observer observer = mAddedObservers.get(0);
-        drain();
-        // no value because it is null
-        assertThat(consumer.mCount, CoreMatchers.is(0));
-        value.set("bla");
-        observer.onInvalidated(tableSet);
-        drain();
-        // get value
-        assertThat(consumer.mCount, CoreMatchers.is(1));
-        observer.onInvalidated(tableSet);
-        drain();
-        // get value
-        assertThat(consumer.mCount, CoreMatchers.is(2));
-        value.set(null);
-        observer.onInvalidated(tableSet);
-        drain();
-        // no value
-        assertThat(consumer.mCount, CoreMatchers.is(2));
-    }
-
-    private void drain() throws InterruptedException {
-        mExecutor.drainTasks(2);
-    }
-
-    @Test
-    public void exception() throws InterruptedException {
-        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, new String[]{"a"},
-                new Callable<String>() {
-                    @Override
-                    public String call() throws Exception {
-                        throw new Exception("i want exception");
-                    }
-                });
-        TestSubscriber<String> subscriber = new TestSubscriber<>();
-        flowable.subscribe(subscriber);
-        drain();
-        assertThat(subscriber.errorCount(), CoreMatchers.is(1));
-        assertThat(subscriber.errors().get(0).getMessage(), CoreMatchers.is("i want exception"));
-    }
-
-    private static class CountingConsumer implements Consumer<Object> {
-        int mCount = 0;
-
-        @Override
-        public void accept(@NonNull Object o) throws Exception {
-            mCount++;
-        }
-    }
-}
diff --git a/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java b/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
new file mode 100644
index 0000000..15a89bf
--- /dev/null
+++ b/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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 androidx.room;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.arch.core.executor.JunitTaskExecutorRule;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.reactivex.Flowable;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.subscribers.TestSubscriber;
+
+@RunWith(JUnit4.class)
+public class RxRoomTest {
+    @Rule
+    public JunitTaskExecutorRule mExecutor = new JunitTaskExecutorRule(1, false);
+    private RoomDatabase mDatabase;
+    private InvalidationTracker mInvalidationTracker;
+    private List<InvalidationTracker.Observer> mAddedObservers = new ArrayList<>();
+
+    @Before
+    public void init() {
+        mDatabase = mock(RoomDatabase.class);
+        mInvalidationTracker = mock(InvalidationTracker.class);
+        when(mDatabase.getInvalidationTracker()).thenReturn(mInvalidationTracker);
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                mAddedObservers.add((InvalidationTracker.Observer) invocation.getArguments()[0]);
+                return null;
+            }
+        }).when(mInvalidationTracker).addObserver(any(InvalidationTracker.Observer.class));
+    }
+
+    @Test
+    public void basicAddRemove() {
+        Flowable<Object> flowable = RxRoom.createFlowable(mDatabase, "a", "b");
+        verify(mInvalidationTracker, never()).addObserver(any(InvalidationTracker.Observer.class));
+        Disposable disposable = flowable.subscribe();
+        verify(mInvalidationTracker).addObserver(any(InvalidationTracker.Observer.class));
+        assertThat(mAddedObservers.size(), CoreMatchers.is(1));
+
+        InvalidationTracker.Observer observer = mAddedObservers.get(0);
+        disposable.dispose();
+
+        verify(mInvalidationTracker).removeObserver(observer);
+
+        disposable = flowable.subscribe();
+        verify(mInvalidationTracker, times(2))
+                .addObserver(any(InvalidationTracker.Observer.class));
+        assertThat(mAddedObservers.size(), CoreMatchers.is(2));
+        assertThat(mAddedObservers.get(1), CoreMatchers.not(CoreMatchers.sameInstance(observer)));
+        InvalidationTracker.Observer observer2 = mAddedObservers.get(1);
+        disposable.dispose();
+        verify(mInvalidationTracker).removeObserver(observer2);
+    }
+
+    @Test
+    public void basicNotify() throws InterruptedException {
+        String[] tables = {"a", "b"};
+        Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
+        Flowable<Object> flowable = RxRoom.createFlowable(mDatabase, tables);
+        CountingConsumer consumer = new CountingConsumer();
+        Disposable disposable = flowable.subscribe(consumer);
+        assertThat(mAddedObservers.size(), CoreMatchers.is(1));
+        InvalidationTracker.Observer observer = mAddedObservers.get(0);
+        assertThat(consumer.mCount, CoreMatchers.is(1));
+        observer.onInvalidated(tableSet);
+        assertThat(consumer.mCount, CoreMatchers.is(2));
+        observer.onInvalidated(tableSet);
+        assertThat(consumer.mCount, CoreMatchers.is(3));
+        disposable.dispose();
+        observer.onInvalidated(tableSet);
+        assertThat(consumer.mCount, CoreMatchers.is(3));
+    }
+
+    @Test
+    public void internalCallable() throws InterruptedException {
+        final AtomicReference<String> value = new AtomicReference<>(null);
+        String[] tables = {"a", "b"};
+        Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
+        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, tables,
+                new Callable<String>() {
+                    @Override
+                    public String call() throws Exception {
+                        return value.get();
+                    }
+                });
+        final CountingConsumer consumer = new CountingConsumer();
+        flowable.subscribe(consumer);
+        InvalidationTracker.Observer observer = mAddedObservers.get(0);
+        drain();
+        // no value because it is null
+        assertThat(consumer.mCount, CoreMatchers.is(0));
+        value.set("bla");
+        observer.onInvalidated(tableSet);
+        drain();
+        // get value
+        assertThat(consumer.mCount, CoreMatchers.is(1));
+        observer.onInvalidated(tableSet);
+        drain();
+        // get value
+        assertThat(consumer.mCount, CoreMatchers.is(2));
+        value.set(null);
+        observer.onInvalidated(tableSet);
+        drain();
+        // no value
+        assertThat(consumer.mCount, CoreMatchers.is(2));
+    }
+
+    private void drain() throws InterruptedException {
+        mExecutor.drainTasks(2);
+    }
+
+    @Test
+    public void exception() throws InterruptedException {
+        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, new String[]{"a"},
+                new Callable<String>() {
+                    @Override
+                    public String call() throws Exception {
+                        throw new Exception("i want exception");
+                    }
+                });
+        TestSubscriber<String> subscriber = new TestSubscriber<>();
+        flowable.subscribe(subscriber);
+        drain();
+        assertThat(subscriber.errorCount(), CoreMatchers.is(1));
+        assertThat(subscriber.errors().get(0).getMessage(), CoreMatchers.is("i want exception"));
+    }
+
+    private static class CountingConsumer implements Consumer<Object> {
+        int mCount = 0;
+
+        @Override
+        public void accept(@NonNull Object o) throws Exception {
+            mCount++;
+        }
+    }
+}
diff --git a/room/testing/api/current.txt b/room/testing/api/current.txt
index e93487f..d441d62 100644
--- a/room/testing/api/current.txt
+++ b/room/testing/api/current.txt
@@ -1,12 +1,12 @@
-package android.arch.persistence.room.testing {
+package androidx.room.testing {
 
   public class MigrationTestHelper extends org.junit.rules.TestWatcher {
     ctor public MigrationTestHelper(android.app.Instrumentation, java.lang.String);
-    ctor public MigrationTestHelper(android.app.Instrumentation, java.lang.String, android.arch.persistence.db.SupportSQLiteOpenHelper.Factory);
-    method public void closeWhenFinished(android.arch.persistence.db.SupportSQLiteDatabase);
-    method public void closeWhenFinished(android.arch.persistence.room.RoomDatabase);
-    method public android.arch.persistence.db.SupportSQLiteDatabase createDatabase(java.lang.String, int) throws java.io.IOException;
-    method public android.arch.persistence.db.SupportSQLiteDatabase runMigrationsAndValidate(java.lang.String, int, boolean, android.arch.persistence.room.migration.Migration...) throws java.io.IOException;
+    ctor public MigrationTestHelper(android.app.Instrumentation, java.lang.String, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void closeWhenFinished(androidx.room.RoomDatabase);
+    method public androidx.sqlite.db.SupportSQLiteDatabase createDatabase(java.lang.String, int) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase runMigrationsAndValidate(java.lang.String, int, boolean, androidx.room.migration.Migration...) throws java.io.IOException;
   }
 
 }
diff --git a/room/testing/api/1.0.0.txt b/room/testing/api_legacy/1.0.0.txt
similarity index 100%
rename from room/testing/api/1.0.0.txt
rename to room/testing/api_legacy/1.0.0.txt
diff --git a/room/testing/api/1.0.0.txt b/room/testing/api_legacy/1.1.0.txt
similarity index 100%
copy from room/testing/api/1.0.0.txt
copy to room/testing/api_legacy/1.1.0.txt
diff --git a/room/testing/api/1.0.0.txt b/room/testing/api_legacy/current.txt
similarity index 100%
copy from room/testing/api/1.0.0.txt
copy to room/testing/api_legacy/current.txt
diff --git a/room/testing/build.gradle b/room/testing/build.gradle
index 48ac8a7..b7162e2 100644
--- a/room/testing/build.gradle
+++ b/room/testing/build.gradle
@@ -24,12 +24,12 @@
 }
 
 dependencies {
-    api(project(":room:common"))
-    api(project(":room:runtime"))
-    api(project(":persistence:db"))
-    api(project(":persistence:db-framework"))
-    api(project(":room:migration"))
-    api(project(":arch:runtime"))
+    api(project(":room:room-common"))
+    api(project(":room:room-runtime"))
+    api(project(":sqlite:sqlite"))
+    api(project(":sqlite:sqlite-framework"))
+    api(project(":room:room-migration"))
+    api(project(":arch:core-runtime"))
     api(SUPPORT_CORE_UTILS, libs.support_exclude_config)
     api(JUNIT)
 }
diff --git a/room/testing/src/main/AndroidManifest.xml b/room/testing/src/main/AndroidManifest.xml
index 594f016..16fc464 100644
--- a/room/testing/src/main/AndroidManifest.xml
+++ b/room/testing/src/main/AndroidManifest.xml
@@ -15,5 +15,5 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.arch.persistence.room.testing">
+          package="androidx.room.testing">
 </manifest>
diff --git a/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java
deleted file mode 100644
index 3d05f8d..0000000
--- a/room/testing/src/main/java/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.testing;
-
-import android.app.Instrumentation;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
-import android.arch.persistence.room.DatabaseConfiguration;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.RoomOpenHelper;
-import android.arch.persistence.room.migration.Migration;
-import android.arch.persistence.room.migration.bundle.DatabaseBundle;
-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;
-import android.database.Cursor;
-import android.util.Log;
-
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A class that can be used in your Instrumentation tests that can create the database in an
- * older schema.
- * <p>
- * You must copy the schema json files (created by passing {@code room.schemaLocation} argument
- * into the annotation processor) into your test assets and pass in the path for that folder into
- * the constructor. This class will read the folder and extract the schemas from there.
- * <pre>
- * android {
- *   defaultConfig {
- *     javaCompileOptions {
- *       annotationProcessorOptions {
- *         arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
- *       }
- *     }
- *   }
- *   sourceSets {
- *     androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
- *   }
- * }
- * </pre>
- */
-public class MigrationTestHelper extends TestWatcher {
-    private static final String TAG = "MigrationTestHelper";
-    private final String mAssetsFolder;
-    private final SupportSQLiteOpenHelper.Factory mOpenFactory;
-    private List<WeakReference<SupportSQLiteDatabase>> mManagedDatabases = new ArrayList<>();
-    private List<WeakReference<RoomDatabase>> mManagedRoomDatabases = new ArrayList<>();
-    private boolean mTestStarted;
-    private Instrumentation mInstrumentation;
-
-    /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
-     * (falls back to the app resources) and the target context to create the database.
-     *
-     * @param instrumentation The instrumentation instance.
-     * @param assetsFolder    The asset folder in the assets directory.
-     */
-    public MigrationTestHelper(Instrumentation instrumentation, String assetsFolder) {
-        this(instrumentation, assetsFolder, new FrameworkSQLiteOpenHelperFactory());
-    }
-
-    /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
-     * (falls back to the app resources) and the target context to create the database.
-     *
-     * @param instrumentation The instrumentation instance.
-     * @param assetsFolder    The asset folder in the assets directory.
-     * @param openFactory     Factory class that allows creation of {@link SupportSQLiteOpenHelper}
-     */
-    public MigrationTestHelper(Instrumentation instrumentation, String assetsFolder,
-            SupportSQLiteOpenHelper.Factory openFactory) {
-        mInstrumentation = instrumentation;
-        if (assetsFolder.endsWith("/")) {
-            assetsFolder = assetsFolder.substring(0, assetsFolder.length() - 1);
-        }
-        mAssetsFolder = assetsFolder;
-        mOpenFactory = openFactory;
-    }
-
-    @Override
-    protected void starting(Description description) {
-        super.starting(description);
-        mTestStarted = true;
-    }
-
-    /**
-     * Creates the database in the given version.
-     * If the database file already exists, it tries to delete it first. If delete fails, throws
-     * an exception.
-     *
-     * @param name    The name of the database.
-     * @param version The version in which the database should be created.
-     * @return A database connection which has the schema in the requested version.
-     * @throws IOException If it cannot find the schema description in the assets folder.
-     */
-    @SuppressWarnings("SameParameterValue")
-    public SupportSQLiteDatabase createDatabase(String name, int version) throws IOException {
-        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
-        if (dbPath.exists()) {
-            Log.d(TAG, "deleting database file " + name);
-            if (!dbPath.delete()) {
-                throw new IllegalStateException("there is a database file and i could not delete"
-                        + " it. Make sure you don't have any open connections to that database"
-                        + " before calling this method.");
-            }
-        }
-        SchemaBundle schemaBundle = loadSchema(version);
-        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
-        DatabaseConfiguration configuration = new DatabaseConfiguration(
-                mInstrumentation.getTargetContext(), name, mOpenFactory, container, null, true,
-                RoomDatabase.JournalMode.TRUNCATE, true, Collections.<Integer>emptySet());
-        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
-                new CreatingDelegate(schemaBundle.getDatabase()),
-                schemaBundle.getDatabase().getIdentityHash(),
-                // we pass the same hash twice since an old schema does not necessarily have
-                // a legacy hash and we would not even persist it.
-                schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, roomOpenHelper);
-    }
-
-    /**
-     * Runs the given set of migrations on the provided database.
-     * <p>
-     * It uses the same algorithm that Room uses to choose migrations so the migrations instances
-     * that are provided to this method must be sufficient to bring the database from current
-     * version to the desired version.
-     * <p>
-     * After the migration, the method validates the database schema to ensure that migration
-     * result matches the expected schema. Handling of dropped tables depends on the
-     * {@code validateDroppedTables} argument. If set to true, the verification will fail if it
-     * finds a table that is not registered in the Database. If set to false, extra tables in the
-     * database will be ignored (this is the runtime library behavior).
-     *
-     * @param name                  The database name. You must first create this database via
-     *                              {@link #createDatabase(String, int)}.
-     * @param version               The final version after applying the migrations.
-     * @param validateDroppedTables If set to true, validation will fail if the database has
-     *                              unknown
-     *                              tables.
-     * @param migrations            The list of available migrations.
-     * @throws IOException           If it cannot find the schema for {@code toVersion}.
-     * @throws IllegalStateException If the schema validation fails.
-     */
-    public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
-            boolean validateDroppedTables, Migration... migrations) throws IOException {
-        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
-        if (!dbPath.exists()) {
-            throw new IllegalStateException("Cannot find the database file for " + name + ". "
-                    + "Before calling runMigrations, you must first create the database via "
-                    + "createDatabase.");
-        }
-        SchemaBundle schemaBundle = loadSchema(version);
-        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
-        container.addMigrations(migrations);
-        DatabaseConfiguration configuration = new DatabaseConfiguration(
-                mInstrumentation.getTargetContext(), name, mOpenFactory, container, null, true,
-                RoomDatabase.JournalMode.TRUNCATE, true, Collections.<Integer>emptySet());
-        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
-                new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),
-                // we pass the same hash twice since an old schema does not necessarily have
-                // a legacy hash and we would not even persist it.
-                schemaBundle.getDatabase().getIdentityHash(),
-                schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, roomOpenHelper);
-    }
-
-    private SupportSQLiteDatabase openDatabase(String name, RoomOpenHelper roomOpenHelper) {
-        SupportSQLiteOpenHelper.Configuration config =
-                SupportSQLiteOpenHelper.Configuration
-                        .builder(mInstrumentation.getTargetContext())
-                        .callback(roomOpenHelper)
-                        .name(name)
-                        .build();
-        SupportSQLiteDatabase db = mOpenFactory.create(config).getWritableDatabase();
-        mManagedDatabases.add(new WeakReference<>(db));
-        return db;
-    }
-
-    @Override
-    protected void finished(Description description) {
-        super.finished(description);
-        for (WeakReference<SupportSQLiteDatabase> dbRef : mManagedDatabases) {
-            SupportSQLiteDatabase db = dbRef.get();
-            if (db != null && db.isOpen()) {
-                try {
-                    db.close();
-                } catch (Throwable ignored) {
-                }
-            }
-        }
-        for (WeakReference<RoomDatabase> dbRef : mManagedRoomDatabases) {
-            final RoomDatabase roomDatabase = dbRef.get();
-            if (roomDatabase != null) {
-                roomDatabase.close();
-            }
-        }
-    }
-
-    /**
-     * Registers a database connection to be automatically closed when the test finishes.
-     * <p>
-     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
-     * {@link org.junit.Rule Rule} annotation.
-     *
-     * @param db The database connection that should be closed after the test finishes.
-     */
-    public void closeWhenFinished(SupportSQLiteDatabase db) {
-        if (!mTestStarted) {
-            throw new IllegalStateException("You cannot register a database to be closed before"
-                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
-                    + " test rule? (@Rule)");
-        }
-        mManagedDatabases.add(new WeakReference<>(db));
-    }
-
-    /**
-     * Registers a database connection to be automatically closed when the test finishes.
-     * <p>
-     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
-     * {@link org.junit.Rule Rule} annotation.
-     *
-     * @param db The RoomDatabase instance which holds the database.
-     */
-    public void closeWhenFinished(RoomDatabase db) {
-        if (!mTestStarted) {
-            throw new IllegalStateException("You cannot register a database to be closed before"
-                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
-                    + " test rule? (@Rule)");
-        }
-        mManagedRoomDatabases.add(new WeakReference<>(db));
-    }
-
-    private SchemaBundle loadSchema(int version) throws IOException {
-        try {
-            return loadSchema(mInstrumentation.getContext(), version);
-        } catch (FileNotFoundException testAssetsIOExceptions) {
-            Log.w(TAG, "Could not find the schema file in the test assets. Checking the"
-                    + " application assets");
-            try {
-                return loadSchema(mInstrumentation.getTargetContext(), version);
-            } catch (FileNotFoundException appAssetsException) {
-                // throw the test assets exception instead
-                throw new FileNotFoundException("Cannot find the schema file in the assets folder. "
-                        + "Make sure to include the exported json schemas in your test assert "
-                        + "inputs. See "
-                        + "https://developer.android.com/topic/libraries/architecture/"
-                        + "room.html#db-migration-testing for details. Missing file: "
-                        + testAssetsIOExceptions.getMessage());
-            }
-        }
-    }
-
-    private SchemaBundle loadSchema(Context context, int version) throws IOException {
-        InputStream input = context.getAssets().open(mAssetsFolder + "/" + version + ".json");
-        return SchemaBundle.deserialize(input);
-    }
-
-    private static TableInfo toTableInfo(EntityBundle entityBundle) {
-        return new TableInfo(entityBundle.getTableName(), toColumnMap(entityBundle),
-                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(
-            List<ForeignKeyBundle> bundles) {
-        if (bundles == null) {
-            return Collections.emptySet();
-        }
-        Set<TableInfo.ForeignKey> result = new HashSet<>(bundles.size());
-        for (ForeignKeyBundle bundle : bundles) {
-            result.add(new TableInfo.ForeignKey(bundle.getTable(),
-                    bundle.getOnDelete(), bundle.getOnUpdate(),
-                    bundle.getColumns(), bundle.getReferencedColumns()));
-        }
-        return result;
-    }
-
-    private static Map<String, TableInfo.Column> toColumnMap(EntityBundle entity) {
-        Map<String, TableInfo.Column> result = new HashMap<>();
-        for (FieldBundle bundle : entity.getFields()) {
-            TableInfo.Column column = toColumn(entity, bundle);
-            result.put(column.name, column);
-        }
-        return result;
-    }
-
-    private static TableInfo.Column toColumn(EntityBundle entity, FieldBundle field) {
-        return new TableInfo.Column(field.getColumnName(), field.getAffinity(),
-                field.isNonNull(), findPrimaryKeyPosition(entity, field));
-    }
-
-    private static int findPrimaryKeyPosition(EntityBundle entity, FieldBundle field) {
-        List<String> columnNames = entity.getPrimaryKey().getColumnNames();
-        int i = 0;
-        for (String columnName : columnNames) {
-            i++;
-            if (field.getColumnName().equalsIgnoreCase(columnName)) {
-                return i;
-            }
-        }
-        return 0;
-    }
-
-    static class MigratingDelegate extends RoomOpenHelperDelegate {
-        private final boolean mVerifyDroppedTables;
-
-        MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
-            super(databaseBundle);
-            mVerifyDroppedTables = verifyDroppedTables;
-        }
-
-        @Override
-        protected void createAllTables(SupportSQLiteDatabase database) {
-            throw new UnsupportedOperationException("Was expecting to migrate but received create."
-                    + "Make sure you have created the database first.");
-        }
-
-        @Override
-        protected void validateMigration(SupportSQLiteDatabase db) {
-            final Map<String, EntityBundle> tables = mDatabaseBundle.getEntitiesByTableName();
-            for (EntityBundle entity : tables.values()) {
-                final TableInfo expected = toTableInfo(entity);
-                final TableInfo found = TableInfo.read(db, entity.getTableName());
-                if (!expected.equals(found)) {
-                    throw new IllegalStateException(
-                            "Migration failed. expected:" + expected + " , found:" + found);
-                }
-            }
-            if (mVerifyDroppedTables) {
-                // now ensure tables that should be removed are removed.
-                Cursor cursor = db.query("SELECT name FROM sqlite_master WHERE type='table'"
-                                + " AND name NOT IN(?, ?, ?)",
-                        new String[]{Room.MASTER_TABLE_NAME, "android_metadata",
-                                "sqlite_sequence"});
-                //noinspection TryFinallyCanBeTryWithResources
-                try {
-                    while (cursor.moveToNext()) {
-                        final String tableName = cursor.getString(0);
-                        if (!tables.containsKey(tableName)) {
-                            throw new IllegalStateException("Migration failed. Unexpected table "
-                                    + tableName);
-                        }
-                    }
-                } finally {
-                    cursor.close();
-                }
-            }
-        }
-    }
-
-    static class CreatingDelegate extends RoomOpenHelperDelegate {
-
-        CreatingDelegate(DatabaseBundle databaseBundle) {
-            super(databaseBundle);
-        }
-
-        @Override
-        protected void createAllTables(SupportSQLiteDatabase database) {
-            for (String query : mDatabaseBundle.buildCreateQueries()) {
-                database.execSQL(query);
-            }
-        }
-
-        @Override
-        protected void validateMigration(SupportSQLiteDatabase db) {
-            throw new UnsupportedOperationException("This open helper just creates the database but"
-                    + " it received a migration request.");
-        }
-    }
-
-    abstract static class RoomOpenHelperDelegate extends RoomOpenHelper.Delegate {
-        final DatabaseBundle mDatabaseBundle;
-
-        RoomOpenHelperDelegate(DatabaseBundle databaseBundle) {
-            super(databaseBundle.getVersion());
-            mDatabaseBundle = databaseBundle;
-        }
-
-        @Override
-        protected void dropAllTables(SupportSQLiteDatabase database) {
-            throw new UnsupportedOperationException("cannot drop all tables in the test");
-        }
-
-        @Override
-        protected void onCreate(SupportSQLiteDatabase database) {
-        }
-
-        @Override
-        protected void onOpen(SupportSQLiteDatabase database) {
-        }
-    }
-}
diff --git a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
new file mode 100644
index 0000000..3708fff
--- /dev/null
+++ b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
@@ -0,0 +1,439 @@
+/*
+ * 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 androidx.room.testing;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+
+import androidx.room.DatabaseConfiguration;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.RoomOpenHelper;
+import androidx.room.migration.Migration;
+import androidx.room.migration.bundle.DatabaseBundle;
+import androidx.room.migration.bundle.EntityBundle;
+import androidx.room.migration.bundle.FieldBundle;
+import androidx.room.migration.bundle.ForeignKeyBundle;
+import androidx.room.migration.bundle.IndexBundle;
+import androidx.room.migration.bundle.SchemaBundle;
+import androidx.room.util.TableInfo;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that can be used in your Instrumentation tests that can create the database in an
+ * older schema.
+ * <p>
+ * You must copy the schema json files (created by passing {@code room.schemaLocation} argument
+ * into the annotation processor) into your test assets and pass in the path for that folder into
+ * the constructor. This class will read the folder and extract the schemas from there.
+ * <pre>
+ * android {
+ *   defaultConfig {
+ *     javaCompileOptions {
+ *       annotationProcessorOptions {
+ *         arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+ *       }
+ *     }
+ *   }
+ *   sourceSets {
+ *     androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
+ *   }
+ * }
+ * </pre>
+ */
+public class MigrationTestHelper extends TestWatcher {
+    private static final String TAG = "MigrationTestHelper";
+    private final String mAssetsFolder;
+    private final SupportSQLiteOpenHelper.Factory mOpenFactory;
+    private List<WeakReference<SupportSQLiteDatabase>> mManagedDatabases = new ArrayList<>();
+    private List<WeakReference<RoomDatabase>> mManagedRoomDatabases = new ArrayList<>();
+    private boolean mTestStarted;
+    private Instrumentation mInstrumentation;
+
+    /**
+     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * (falls back to the app resources) and the target context to create the database.
+     *
+     * @param instrumentation The instrumentation instance.
+     * @param assetsFolder    The asset folder in the assets directory.
+     */
+    public MigrationTestHelper(Instrumentation instrumentation, String assetsFolder) {
+        this(instrumentation, assetsFolder, new FrameworkSQLiteOpenHelperFactory());
+    }
+
+    /**
+     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * (falls back to the app resources) and the target context to create the database.
+     *
+     * @param instrumentation The instrumentation instance.
+     * @param assetsFolder    The asset folder in the assets directory.
+     * @param openFactory     Factory class that allows creation of {@link SupportSQLiteOpenHelper}
+     */
+    public MigrationTestHelper(Instrumentation instrumentation, String assetsFolder,
+            SupportSQLiteOpenHelper.Factory openFactory) {
+        mInstrumentation = instrumentation;
+        if (assetsFolder.endsWith("/")) {
+            assetsFolder = assetsFolder.substring(0, assetsFolder.length() - 1);
+        }
+        mAssetsFolder = assetsFolder;
+        mOpenFactory = openFactory;
+    }
+
+    @Override
+    protected void starting(Description description) {
+        super.starting(description);
+        mTestStarted = true;
+    }
+
+    /**
+     * Creates the database in the given version.
+     * If the database file already exists, it tries to delete it first. If delete fails, throws
+     * an exception.
+     *
+     * @param name    The name of the database.
+     * @param version The version in which the database should be created.
+     * @return A database connection which has the schema in the requested version.
+     * @throws IOException If it cannot find the schema description in the assets folder.
+     */
+    @SuppressWarnings("SameParameterValue")
+    public SupportSQLiteDatabase createDatabase(String name, int version) throws IOException {
+        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
+        if (dbPath.exists()) {
+            Log.d(TAG, "deleting database file " + name);
+            if (!dbPath.delete()) {
+                throw new IllegalStateException("there is a database file and i could not delete"
+                        + " it. Make sure you don't have any open connections to that database"
+                        + " before calling this method.");
+            }
+        }
+        SchemaBundle schemaBundle = loadSchema(version);
+        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
+        DatabaseConfiguration configuration = new DatabaseConfiguration(
+                mInstrumentation.getTargetContext(), name, mOpenFactory, container, null, true,
+                RoomDatabase.JournalMode.TRUNCATE, true, Collections.<Integer>emptySet());
+        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
+                new CreatingDelegate(schemaBundle.getDatabase()),
+                schemaBundle.getDatabase().getIdentityHash(),
+                // we pass the same hash twice since an old schema does not necessarily have
+                // a legacy hash and we would not even persist it.
+                schemaBundle.getDatabase().getIdentityHash());
+        return openDatabase(name, roomOpenHelper);
+    }
+
+    /**
+     * Runs the given set of migrations on the provided database.
+     * <p>
+     * It uses the same algorithm that Room uses to choose migrations so the migrations instances
+     * that are provided to this method must be sufficient to bring the database from current
+     * version to the desired version.
+     * <p>
+     * After the migration, the method validates the database schema to ensure that migration
+     * result matches the expected schema. Handling of dropped tables depends on the
+     * {@code validateDroppedTables} argument. If set to true, the verification will fail if it
+     * finds a table that is not registered in the Database. If set to false, extra tables in the
+     * database will be ignored (this is the runtime library behavior).
+     *
+     * @param name                  The database name. You must first create this database via
+     *                              {@link #createDatabase(String, int)}.
+     * @param version               The final version after applying the migrations.
+     * @param validateDroppedTables If set to true, validation will fail if the database has
+     *                              unknown
+     *                              tables.
+     * @param migrations            The list of available migrations.
+     * @throws IOException           If it cannot find the schema for {@code toVersion}.
+     * @throws IllegalStateException If the schema validation fails.
+     */
+    public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
+            boolean validateDroppedTables, Migration... migrations) throws IOException {
+        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
+        if (!dbPath.exists()) {
+            throw new IllegalStateException("Cannot find the database file for " + name + ". "
+                    + "Before calling runMigrations, you must first create the database via "
+                    + "createDatabase.");
+        }
+        SchemaBundle schemaBundle = loadSchema(version);
+        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
+        container.addMigrations(migrations);
+        DatabaseConfiguration configuration = new DatabaseConfiguration(
+                mInstrumentation.getTargetContext(), name, mOpenFactory, container, null, true,
+                RoomDatabase.JournalMode.TRUNCATE, true, Collections.<Integer>emptySet());
+        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
+                new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),
+                // we pass the same hash twice since an old schema does not necessarily have
+                // a legacy hash and we would not even persist it.
+                schemaBundle.getDatabase().getIdentityHash(),
+                schemaBundle.getDatabase().getIdentityHash());
+        return openDatabase(name, roomOpenHelper);
+    }
+
+    private SupportSQLiteDatabase openDatabase(String name, RoomOpenHelper roomOpenHelper) {
+        SupportSQLiteOpenHelper.Configuration config =
+                SupportSQLiteOpenHelper.Configuration
+                        .builder(mInstrumentation.getTargetContext())
+                        .callback(roomOpenHelper)
+                        .name(name)
+                        .build();
+        SupportSQLiteDatabase db = mOpenFactory.create(config).getWritableDatabase();
+        mManagedDatabases.add(new WeakReference<>(db));
+        return db;
+    }
+
+    @Override
+    protected void finished(Description description) {
+        super.finished(description);
+        for (WeakReference<SupportSQLiteDatabase> dbRef : mManagedDatabases) {
+            SupportSQLiteDatabase db = dbRef.get();
+            if (db != null && db.isOpen()) {
+                try {
+                    db.close();
+                } catch (Throwable ignored) {
+                }
+            }
+        }
+        for (WeakReference<RoomDatabase> dbRef : mManagedRoomDatabases) {
+            final RoomDatabase roomDatabase = dbRef.get();
+            if (roomDatabase != null) {
+                roomDatabase.close();
+            }
+        }
+    }
+
+    /**
+     * Registers a database connection to be automatically closed when the test finishes.
+     * <p>
+     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
+     * {@link org.junit.Rule Rule} annotation.
+     *
+     * @param db The database connection that should be closed after the test finishes.
+     */
+    public void closeWhenFinished(SupportSQLiteDatabase db) {
+        if (!mTestStarted) {
+            throw new IllegalStateException("You cannot register a database to be closed before"
+                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
+                    + " test rule? (@Rule)");
+        }
+        mManagedDatabases.add(new WeakReference<>(db));
+    }
+
+    /**
+     * Registers a database connection to be automatically closed when the test finishes.
+     * <p>
+     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
+     * {@link org.junit.Rule Rule} annotation.
+     *
+     * @param db The RoomDatabase instance which holds the database.
+     */
+    public void closeWhenFinished(RoomDatabase db) {
+        if (!mTestStarted) {
+            throw new IllegalStateException("You cannot register a database to be closed before"
+                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
+                    + " test rule? (@Rule)");
+        }
+        mManagedRoomDatabases.add(new WeakReference<>(db));
+    }
+
+    private SchemaBundle loadSchema(int version) throws IOException {
+        try {
+            return loadSchema(mInstrumentation.getContext(), version);
+        } catch (FileNotFoundException testAssetsIOExceptions) {
+            Log.w(TAG, "Could not find the schema file in the test assets. Checking the"
+                    + " application assets");
+            try {
+                return loadSchema(mInstrumentation.getTargetContext(), version);
+            } catch (FileNotFoundException appAssetsException) {
+                // throw the test assets exception instead
+                throw new FileNotFoundException("Cannot find the schema file in the assets folder. "
+                        + "Make sure to include the exported json schemas in your test assert "
+                        + "inputs. See "
+                        + "https://developer.android.com/topic/libraries/architecture/"
+                        + "room.html#db-migration-testing for details. Missing file: "
+                        + testAssetsIOExceptions.getMessage());
+            }
+        }
+    }
+
+    private SchemaBundle loadSchema(Context context, int version) throws IOException {
+        InputStream input = context.getAssets().open(mAssetsFolder + "/" + version + ".json");
+        return SchemaBundle.deserialize(input);
+    }
+
+    private static TableInfo toTableInfo(EntityBundle entityBundle) {
+        return new TableInfo(entityBundle.getTableName(), toColumnMap(entityBundle),
+                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(
+            List<ForeignKeyBundle> bundles) {
+        if (bundles == null) {
+            return Collections.emptySet();
+        }
+        Set<TableInfo.ForeignKey> result = new HashSet<>(bundles.size());
+        for (ForeignKeyBundle bundle : bundles) {
+            result.add(new TableInfo.ForeignKey(bundle.getTable(),
+                    bundle.getOnDelete(), bundle.getOnUpdate(),
+                    bundle.getColumns(), bundle.getReferencedColumns()));
+        }
+        return result;
+    }
+
+    private static Map<String, TableInfo.Column> toColumnMap(EntityBundle entity) {
+        Map<String, TableInfo.Column> result = new HashMap<>();
+        for (FieldBundle bundle : entity.getFields()) {
+            TableInfo.Column column = toColumn(entity, bundle);
+            result.put(column.name, column);
+        }
+        return result;
+    }
+
+    private static TableInfo.Column toColumn(EntityBundle entity, FieldBundle field) {
+        return new TableInfo.Column(field.getColumnName(), field.getAffinity(),
+                field.isNonNull(), findPrimaryKeyPosition(entity, field));
+    }
+
+    private static int findPrimaryKeyPosition(EntityBundle entity, FieldBundle field) {
+        List<String> columnNames = entity.getPrimaryKey().getColumnNames();
+        int i = 0;
+        for (String columnName : columnNames) {
+            i++;
+            if (field.getColumnName().equalsIgnoreCase(columnName)) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    static class MigratingDelegate extends RoomOpenHelperDelegate {
+        private final boolean mVerifyDroppedTables;
+
+        MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
+            super(databaseBundle);
+            mVerifyDroppedTables = verifyDroppedTables;
+        }
+
+        @Override
+        protected void createAllTables(SupportSQLiteDatabase database) {
+            throw new UnsupportedOperationException("Was expecting to migrate but received create."
+                    + "Make sure you have created the database first.");
+        }
+
+        @Override
+        protected void validateMigration(SupportSQLiteDatabase db) {
+            final Map<String, EntityBundle> tables = mDatabaseBundle.getEntitiesByTableName();
+            for (EntityBundle entity : tables.values()) {
+                final TableInfo expected = toTableInfo(entity);
+                final TableInfo found = TableInfo.read(db, entity.getTableName());
+                if (!expected.equals(found)) {
+                    throw new IllegalStateException(
+                            "Migration failed. expected:" + expected + " , found:" + found);
+                }
+            }
+            if (mVerifyDroppedTables) {
+                // now ensure tables that should be removed are removed.
+                Cursor cursor = db.query("SELECT name FROM sqlite_master WHERE type='table'"
+                                + " AND name NOT IN(?, ?, ?)",
+                        new String[]{Room.MASTER_TABLE_NAME, "android_metadata",
+                                "sqlite_sequence"});
+                //noinspection TryFinallyCanBeTryWithResources
+                try {
+                    while (cursor.moveToNext()) {
+                        final String tableName = cursor.getString(0);
+                        if (!tables.containsKey(tableName)) {
+                            throw new IllegalStateException("Migration failed. Unexpected table "
+                                    + tableName);
+                        }
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    static class CreatingDelegate extends RoomOpenHelperDelegate {
+
+        CreatingDelegate(DatabaseBundle databaseBundle) {
+            super(databaseBundle);
+        }
+
+        @Override
+        protected void createAllTables(SupportSQLiteDatabase database) {
+            for (String query : mDatabaseBundle.buildCreateQueries()) {
+                database.execSQL(query);
+            }
+        }
+
+        @Override
+        protected void validateMigration(SupportSQLiteDatabase db) {
+            throw new UnsupportedOperationException("This open helper just creates the database but"
+                    + " it received a migration request.");
+        }
+    }
+
+    abstract static class RoomOpenHelperDelegate extends RoomOpenHelper.Delegate {
+        final DatabaseBundle mDatabaseBundle;
+
+        RoomOpenHelperDelegate(DatabaseBundle databaseBundle) {
+            super(databaseBundle.getVersion());
+            mDatabaseBundle = databaseBundle;
+        }
+
+        @Override
+        protected void dropAllTables(SupportSQLiteDatabase database) {
+            throw new UnsupportedOperationException("cannot drop all tables in the test");
+        }
+
+        @Override
+        protected void onCreate(SupportSQLiteDatabase database) {
+        }
+
+        @Override
+        protected void onOpen(SupportSQLiteDatabase database) {
+        }
+    }
+}
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/BrowseFragment.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/BrowseFragment.java
index aa53d49..16bd6be 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/BrowseFragment.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/BrowseFragment.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,8 +35,6 @@
 
 import androidx.core.content.ContextCompat;
 import androidx.fragment.app.Fragment;
-import androidx.media.MediaBrowserCompat;
-import androidx.media.session.MediaControllerCompat;
 
 import com.example.android.supportv4.R;
 
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserServiceSupport.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserServiceSupport.java
index 0e6701e..dc0cd81 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserServiceSupport.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserServiceSupport.java
@@ -29,6 +29,8 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaSessionCompat;
@@ -36,8 +38,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import androidx.media.MediaBrowserCompat;
-import androidx.media.MediaBrowserCompat.MediaItem;
 import androidx.media.MediaBrowserServiceCompat;
 import androidx.media.session.MediaButtonReceiver;
 
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserSupport.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserSupport.java
index bf8e27c..8dd2408 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserSupport.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaBrowserSupport.java
@@ -17,10 +17,10 @@
 package com.example.android.supportv4.media;
 
 import android.os.Bundle;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 
 import androidx.fragment.app.FragmentActivity;
-import androidx.media.MediaBrowserCompat;
-import androidx.media.session.MediaControllerCompat;
 
 import com.example.android.supportv4.R;
 
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaNotificationManager.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaNotificationManager.java
index 5a118e6..c6bf935 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaNotificationManager.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/MediaNotificationManager.java
@@ -28,13 +28,13 @@
 import android.os.RemoteException;
 import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
 import androidx.core.app.NotificationCompat;
 import androidx.core.app.NotificationManagerCompat;
-import androidx.media.session.MediaControllerCompat;
 
 import com.example.android.supportv4.R;
 import com.example.android.supportv4.media.utils.ResourceHelper;
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/QueueFragment.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/QueueFragment.java
index 55e54c8..7ca8c9c 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/QueueFragment.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/media/QueueFragment.java
@@ -19,6 +19,8 @@
 import android.content.ComponentName;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
@@ -31,8 +33,6 @@
 
 import androidx.core.content.ContextCompat;
 import androidx.fragment.app.Fragment;
-import androidx.media.MediaBrowserCompat;
-import androidx.media.session.MediaControllerCompat;
 
 import com.example.android.supportv4.R;
 
diff --git a/samples/SupportDesignDemos/build.gradle b/samples/SupportDesignDemos/build.gradle
index b554991..8721e2b 100644
--- a/samples/SupportDesignDemos/build.gradle
+++ b/samples/SupportDesignDemos/build.gradle
@@ -1,6 +1,5 @@
 plugins {
     id("SupportAndroidTestAppPlugin")
-    id("androidx.tools.jetifier")
 }
 
 dependencies {
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/MusicPlayerFragment.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/MusicPlayerFragment.java
index 6106355..1fd7af1 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/MusicPlayerFragment.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/MusicPlayerFragment.java
@@ -22,6 +22,7 @@
 import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 
 import androidx.leanback.app.PlaybackFragment;
@@ -32,7 +33,6 @@
 import androidx.leanback.widget.Action;
 import androidx.leanback.widget.ArrayObjectAdapter;
 import androidx.leanback.widget.PlaybackControlsRow;
-import androidx.media.session.MediaControllerCompat;
 
 import com.google.gson.Gson;
 
diff --git a/samples/SupportLeanbackJank/build.gradle b/samples/SupportLeanbackJank/build.gradle
index 43ed95d..199e545 100644
--- a/samples/SupportLeanbackJank/build.gradle
+++ b/samples/SupportLeanbackJank/build.gradle
@@ -1,10 +1,9 @@
 plugins {
     id("SupportAndroidTestAppPlugin")
-    id("androidx.tools.jetifier")
 }
 
 dependencies {
-    implementation(jetifier.process('com.github.bumptech.glide:glide:3.6.1'))
+    implementation('com.github.bumptech.glide:glide:3.6.1')
     implementation(project(":leanback"))
     implementation(project(":leanback-preference"))
 }
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
index dfb82f5..379b991 100644
--- a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SampleSliceProvider.java
@@ -39,7 +39,7 @@
 import androidx.annotation.NonNull;
 import androidx.slice.Slice;
 import androidx.slice.SliceProvider;
-import androidx.slice.builders.GridBuilder;
+import androidx.slice.builders.GridRowBuilder;
 import androidx.slice.builders.ListBuilder;
 import androidx.slice.builders.MessagingSliceBuilder;
 import androidx.slice.builders.SliceAction;
@@ -214,10 +214,10 @@
 
     private Slice createCatSlice(Uri sliceUri, boolean customSeeMore) {
         ListBuilder b = new ListBuilder(getContext(), sliceUri);
-        GridBuilder gb = new GridBuilder(b);
+        GridRowBuilder gb = new GridRowBuilder(b);
         PendingIntent pi = getBroadcastIntent(ACTION_TOAST, "See cats you follow");
         if (customSeeMore) {
-            GridBuilder.CellBuilder cb = new GridBuilder.CellBuilder(gb);
+            GridRowBuilder.CellBuilder cb = new GridRowBuilder.CellBuilder(gb);
             cb.addImage(Icon.createWithResource(getContext(), R.drawable.ic_right_caret),
                     ICON_IMAGE);
             cb.setContentIntent(pi);
@@ -226,59 +226,59 @@
         } else {
             gb.addSeeMoreAction(pi);
         }
-        gb.addCell(new GridBuilder.CellBuilder(gb)
+        gb.addCell(new GridRowBuilder.CellBuilder(gb)
                     .addImage(Icon.createWithResource(getContext(), R.drawable.cat_1), SMALL_IMAGE)
                     .addTitleText("Oreo"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_2),
                                 SMALL_IMAGE)
                         .addTitleText("Silver"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_3),
                                 SMALL_IMAGE)
                         .addTitleText("Drake"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_5),
                                 SMALL_IMAGE)
                         .addTitleText("Olive"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_4),
                                 SMALL_IMAGE)
                         .addTitleText("Lady Marmalade"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_6),
                                 SMALL_IMAGE)
                         .addTitleText("Grapefruit"));
-        return b.addGrid(gb).build();
+        return b.addGridRow(gb).build();
     }
 
     private Slice createContact2(Uri sliceUri) {
         ListBuilder b = new ListBuilder(getContext(), sliceUri);
         ListBuilder.RowBuilder rb = new ListBuilder.RowBuilder(b);
-        GridBuilder gb = new GridBuilder(b);
+        GridRowBuilder gb = new GridRowBuilder(b);
         return b.setColor(0xff3949ab)
                 .addRow(rb
                         .setTitle("Mady Pitza")
                         .setSubtitle("Frequently contacted contact")
                         .addEndItem(Icon.createWithResource(getContext(), R.drawable.mady),
                                 SMALL_IMAGE))
-                .addGrid(gb
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                .addGridRow(gb
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                                 .addImage(Icon.createWithResource(getContext(), R.drawable.ic_call),
                                         ICON_IMAGE)
                                 .addText("Call")
                                 .setContentIntent(getBroadcastIntent(ACTION_TOAST, "call")))
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                                 .addImage(Icon.createWithResource(getContext(), R.drawable.ic_text),
                                         ICON_IMAGE)
                                 .addText("Text")
                                 .setContentIntent(getBroadcastIntent(ACTION_TOAST, "text")))
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                                 .addImage(Icon.createWithResource(getContext(),
                                         R.drawable.ic_video), ICON_IMAGE)
                                 .setContentIntent(getBroadcastIntent(ACTION_TOAST, "video"))
                                 .addText("Video"))
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                                 .addImage(Icon.createWithResource(getContext(),
                                         R.drawable.ic_email), ICON_IMAGE)
                                 .addText("Email")
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
index a5b77e3..174705e 100644
--- a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
@@ -19,7 +19,6 @@
 import static com.example.androidx.slice.demos.SampleSliceProvider.URI_PATHS;
 import static com.example.androidx.slice.demos.SampleSliceProvider.getUri;
 
-import android.arch.lifecycle.LiveData;
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -45,6 +44,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.widget.Toolbar;
+import androidx.lifecycle.LiveData;
 import androidx.slice.Slice;
 import androidx.slice.SliceItem;
 import androidx.slice.widget.EventInfo;
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.java
index 666f7d2..bdf0b49 100644
--- a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.java
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2018 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.
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/CardView.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/CardView.java
index 02b52f8..563f73d 100644
--- a/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/CardView.java
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/viewpager2/cards/CardView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2018 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.
diff --git a/settings.gradle b/settings.gradle
index c402bf3..dde4d2f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,19 +33,26 @@
 //
 /////////////////////////////
 
-includeProject(":vectordrawable-animated", "graphics/drawable/animated")
+includeProject(":annotation", "annotations")
 includeProject(":appcompat", "v7/appcompat")
 includeProject(":asynclayoutinflater", "asynclayoutinflater")
+includeProject(":browser", "browser")
 includeProject(":car", "car")
 includeProject(":cardview", "cardview")
 includeProject(":collection", "collection")
+includeProject(":contentpaging", "content")
 includeProject(":coordinatorlayout", "coordinatorlayout")
+includeProject(":core", "compat")
 includeProject(":cursoradapter", "cursoradapter")
-includeProject(":browser", "browser")
 includeProject(":customview", "customview")
 includeProject(":documentfile", "documentfile")
 includeProject(":drawerlayout", "drawerlayout")
+includeProject(":dynamicanimation", "dynamic-animation")
+includeProject(":emoji", "emoji/core")
+includeProject(":emoji-bundled", "emoji/bundled")
+includeProject(":emoji-appcompat", "emoji/appcompat")
 includeProject(":exifinterface", "exifinterface")
+includeProject(":fragment", "fragment")
 includeProject(":gridlayout", "gridlayout")
 includeProject(":heifwriter", "heifwriter")
 includeProject(":interpolator", "interpolator")
@@ -54,13 +61,14 @@
 includeProject(":jetifier-standalone", "jetifier/jetifier/standalone")
 includeProject(":jetifier-preprocessor", "jetifier/jetifier/preprocessor")
 includeProject(":leanback", "leanback")
+includeProject(":leanback-preference", "leanback-preference")
 includeProject(":loader", "loader")
 includeProject(":localbroadcastmanager", "localbroadcastmanager")
+includeProject(":media", "media")
 includeProject(":mediarouter", "mediarouter")
 includeProject(":palette", "palette")
 includeProject(":percentlayout", "percent")
 includeProject(":preference", "preference")
-includeProject(":leanback-preference", "leanback-preference")
 includeProject(":print", "print")
 includeProject(":recommendation", "recommendation")
 includeProject(":recyclerview", "v7/recyclerview")
@@ -69,20 +77,12 @@
 includeProject(":slices-view", "slices/view")
 includeProject(":slices-builders", "slices/builders")
 includeProject(":slidingpanelayout", "slidingpanelayout")
-includeProject(":annotation", "annotations")
-includeProject(":core", "compat")
-includeProject(":contentpaging", "content")
-includeProject(":dynamicanimation", "dynamic-animation")
-includeProject(":emoji", "emoji/core")
-includeProject(":emoji-bundled", "emoji/bundled")
-includeProject(":emoji-appcompat", "emoji/appcompat")
-includeProject(":fragment", "fragment")
-includeProject(":media", "media")
-includeProject(":tvprovider", "tv-provider")
-includeProject(":vectordrawable", "graphics/drawable/static")
 includeProject(":swiperefreshlayout", "swiperefreshlayout")
 includeProject(":textclassifier", "textclassifier")
 includeProject(":transition", "transition")
+includeProject(":tvprovider", "tv-provider")
+includeProject(":vectordrawable", "graphics/drawable/static")
+includeProject(":vectordrawable-animated", "graphics/drawable/animated")
 includeProject(":viewpager", "viewpager")
 includeProject(":viewpager2", "viewpager2")
 includeProject(":wear", "wear")
@@ -116,7 +116,7 @@
 includeProject(":support-design-demos", new File(samplesRoot, "SupportDesignDemos"))
 includeProject(":support-emoji-demos", new File(samplesRoot, "SupportEmojiDemos"))
 includeProject(":support-leanback-demos", new File(samplesRoot, "SupportLeanbackDemos"))
-includeProject(":support-leanback-jank", new File(samplesRoot, "SupportLeanbackJank"))
+//includeProject(":support-leanback-jank", new File(samplesRoot, "SupportLeanbackJank"))
 includeProject(":support-percent-demos", new File(samplesRoot, "SupportPercentDemos"))
 includeProject(":support-preference-demos", new File(samplesRoot, "SupportPreferenceDemos"))
 includeProject(":support-slices-demos", new File(samplesRoot, "SupportSliceDemos"))
diff --git a/slices/builders/api/current.txt b/slices/builders/api/current.txt
index 96b8d75..ed3d684 100644
--- a/slices/builders/api/current.txt
+++ b/slices/builders/api/current.txt
@@ -1,6 +1,6 @@
 package androidx.slice.builders {
 
-  public class GridBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+  public deprecated class GridBuilder extends androidx.slice.builders.TemplateSliceBuilder {
     ctor public GridBuilder(androidx.slice.builders.ListBuilder);
     method public androidx.slice.builders.GridBuilder addCell(androidx.slice.builders.GridBuilder.CellBuilder);
     method public androidx.slice.builders.GridBuilder addCell(java.util.function.Consumer<androidx.slice.builders.GridBuilder.CellBuilder>);
@@ -14,7 +14,7 @@
     field public static final deprecated int SMALL_IMAGE = 1; // 0x1
   }
 
-  public static final class GridBuilder.CellBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+  public static final deprecated class GridBuilder.CellBuilder extends androidx.slice.builders.TemplateSliceBuilder {
     ctor public GridBuilder.CellBuilder(androidx.slice.builders.GridBuilder);
     ctor public GridBuilder.CellBuilder(androidx.slice.builders.GridBuilder, android.net.Uri);
     method public deprecated androidx.slice.builders.GridBuilder.CellBuilder addImage(android.graphics.drawable.Icon);
@@ -31,11 +31,37 @@
     method public androidx.slice.builders.GridBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
   }
 
+  public class GridRowBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+    ctor public GridRowBuilder(androidx.slice.builders.ListBuilder);
+    method public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+    method public androidx.slice.builders.GridRowBuilder addCell(java.util.function.Consumer<androidx.slice.builders.GridRowBuilder.CellBuilder>);
+    method public androidx.slice.builders.GridRowBuilder addSeeMoreAction(android.app.PendingIntent);
+    method public androidx.slice.builders.GridRowBuilder addSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+    method public androidx.slice.builders.GridRowBuilder addSeeMoreCell(java.util.function.Consumer<androidx.slice.builders.GridRowBuilder.CellBuilder>);
+    method public androidx.slice.builders.GridRowBuilder setContentDescription(java.lang.CharSequence);
+    method public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+  }
+
+  public static final class GridRowBuilder.CellBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+    ctor public GridRowBuilder.CellBuilder(androidx.slice.builders.GridRowBuilder);
+    ctor public GridRowBuilder.CellBuilder(androidx.slice.builders.GridRowBuilder, android.net.Uri);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(android.graphics.drawable.Icon, int);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(android.graphics.drawable.Icon, int, boolean);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(java.lang.CharSequence);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(java.lang.CharSequence, boolean);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(java.lang.CharSequence);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(java.lang.CharSequence, boolean);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(java.lang.CharSequence);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
+  }
+
   public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
     ctor public ListBuilder(android.content.Context, android.net.Uri);
     method public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder addGrid(androidx.slice.builders.GridBuilder);
-    method public androidx.slice.builders.ListBuilder addGrid(java.util.function.Consumer<androidx.slice.builders.GridBuilder>);
+    method public deprecated androidx.slice.builders.ListBuilder addGrid(androidx.slice.builders.GridBuilder);
+    method public deprecated androidx.slice.builders.ListBuilder addGrid(java.util.function.Consumer<androidx.slice.builders.GridBuilder>);
+    method public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
+    method public androidx.slice.builders.ListBuilder addGridRow(java.util.function.Consumer<androidx.slice.builders.GridRowBuilder>);
     method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
     method public androidx.slice.builders.ListBuilder addInputRange(java.util.function.Consumer<androidx.slice.builders.ListBuilder.InputRangeBuilder>);
     method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
diff --git a/slices/builders/api_legacy/current.txt b/slices/builders/api_legacy/current.txt
index 5aa4f42..8b00abf 100644
--- a/slices/builders/api_legacy/current.txt
+++ b/slices/builders/api_legacy/current.txt
@@ -2,38 +2,38 @@
 
   public class GridBuilder extends androidx.slice.builders.TemplateSliceBuilder {
     ctor public GridBuilder(androidx.slice.builders.ListBuilder);
-    method public androidx.slice.builders.GridBuilder addCell(androidx.slice.builders.GridBuilder.CellBuilder);
-    method public androidx.slice.builders.GridBuilder addCell(java.util.function.Consumer<androidx.slice.builders.GridBuilder.CellBuilder>);
-    method public androidx.slice.builders.GridBuilder addSeeMoreAction(android.app.PendingIntent);
-    method public androidx.slice.builders.GridBuilder addSeeMoreCell(androidx.slice.builders.GridBuilder.CellBuilder);
-    method public androidx.slice.builders.GridBuilder addSeeMoreCell(java.util.function.Consumer<androidx.slice.builders.GridBuilder.CellBuilder>);
-    method public androidx.slice.builders.GridBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+    method public androidx.slice.builders.GridRowBuilder addCell(java.util.function.Consumer<androidx.slice.builders.GridRowBuilder.CellBuilder>);
+    method public androidx.slice.builders.GridRowBuilder addSeeMoreAction(android.app.PendingIntent);
+    method public androidx.slice.builders.GridRowBuilder addSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+    method public androidx.slice.builders.GridRowBuilder addSeeMoreCell(java.util.function.Consumer<androidx.slice.builders.GridRowBuilder.CellBuilder>);
+    method public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
     field public static final deprecated int ICON_IMAGE = 0; // 0x0
     field public static final deprecated int LARGE_IMAGE = 2; // 0x2
     field public static final deprecated int SMALL_IMAGE = 1; // 0x1
   }
 
   public static final class GridBuilder.CellBuilder extends androidx.slice.builders.TemplateSliceBuilder {
-    ctor public GridBuilder.CellBuilder(androidx.slice.builders.GridBuilder);
-    ctor public GridBuilder.CellBuilder(androidx.slice.builders.GridBuilder, android.net.Uri);
-    method public deprecated androidx.slice.builders.GridBuilder.CellBuilder addImage(android.graphics.drawable.Icon);
-    method public deprecated androidx.slice.builders.GridBuilder.CellBuilder addImage(android.graphics.drawable.Icon, boolean);
-    method public androidx.slice.builders.GridBuilder.CellBuilder addImage(android.graphics.drawable.Icon, int);
-    method public androidx.slice.builders.GridBuilder.CellBuilder addImage(android.graphics.drawable.Icon, int, boolean);
-    method public deprecated androidx.slice.builders.GridBuilder.CellBuilder addLargeImage(android.graphics.drawable.Icon);
-    method public deprecated androidx.slice.builders.GridBuilder.CellBuilder addLargeImage(android.graphics.drawable.Icon, boolean);
-    method public androidx.slice.builders.GridBuilder.CellBuilder addText(java.lang.CharSequence);
-    method public androidx.slice.builders.GridBuilder.CellBuilder addText(java.lang.CharSequence, boolean);
-    method public androidx.slice.builders.GridBuilder.CellBuilder addTitleText(java.lang.CharSequence);
-    method public androidx.slice.builders.GridBuilder.CellBuilder addTitleText(java.lang.CharSequence, boolean);
-    method public androidx.slice.builders.GridBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
+    ctor public GridBuilder.CellBuilder(androidx.slice.builders.GridRowBuilder);
+    ctor public GridBuilder.CellBuilder(androidx.slice.builders.GridRowBuilder, android.net.Uri);
+    method public deprecated androidx.slice.builders.GridRowBuilder.CellBuilder addImage(android.graphics.drawable.Icon);
+    method public deprecated androidx.slice.builders.GridRowBuilder.CellBuilder addImage(android.graphics.drawable.Icon, boolean);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(android.graphics.drawable.Icon, int);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(android.graphics.drawable.Icon, int, boolean);
+    method public deprecated androidx.slice.builders.GridRowBuilder.CellBuilder addLargeImage(android.graphics.drawable.Icon);
+    method public deprecated androidx.slice.builders.GridRowBuilder.CellBuilder addLargeImage(android.graphics.drawable.Icon, boolean);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(java.lang.CharSequence);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(java.lang.CharSequence, boolean);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(java.lang.CharSequence);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(java.lang.CharSequence, boolean);
+    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
   }
 
   public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
     ctor public ListBuilder(android.content.Context, android.net.Uri);
     method public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder addGrid(androidx.slice.builders.GridBuilder);
-    method public androidx.slice.builders.ListBuilder addGrid(java.util.function.Consumer<androidx.slice.builders.GridBuilder>);
+    method public androidx.slice.builders.ListBuilder addGrid(androidx.slice.builders.GridRowBuilder);
+    method public androidx.slice.builders.ListBuilder addGrid(java.util.function.Consumer<androidx.slice.builders.GridRowBuilder>);
     method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
     method public androidx.slice.builders.ListBuilder addInputRange(java.util.function.Consumer<androidx.slice.builders.ListBuilder.InputRangeBuilder>);
     method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
diff --git a/slices/builders/src/main/java/androidx/slice/builders/GridBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/GridBuilder.java
index 488d925..87e7ddb 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/GridBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/GridBuilder.java
@@ -23,14 +23,14 @@
 import android.net.Uri;
 import android.os.Build;
 
-import java.util.function.Consumer;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.slice.builders.impl.TemplateBuilderImpl;
 
+import java.util.function.Consumer;
+
 
 /**
  * Builder to construct a row of slice content in a grid format.
@@ -38,10 +38,12 @@
  * A grid row is composed of cells, each cell can have a combination of text and images. For more
  * details see {@link CellBuilder}.
  * </p>
+ * @deprecated TO BE REMOVED; please use {@link GridRowBuilder} instead.
  */
+@Deprecated
 public class GridBuilder extends TemplateSliceBuilder {
 
-    private androidx.slice.builders.impl.GridBuilder mImpl;
+    private androidx.slice.builders.impl.GridRowBuilder mImpl;
     private boolean mHasSeeMore;
 
     /**
@@ -76,7 +78,7 @@
 
     @Override
     void setImpl(TemplateBuilderImpl impl) {
-        mImpl = (androidx.slice.builders.impl.GridBuilder) impl;
+        mImpl = (androidx.slice.builders.impl.GridRowBuilder) impl;
     }
 
     /**
@@ -190,7 +192,7 @@
      * @hide
      */
     @RestrictTo(LIBRARY)
-    public androidx.slice.builders.impl.GridBuilder getImpl() {
+    public androidx.slice.builders.impl.GridRowBuilder getImpl() {
         return mImpl;
     }
 
@@ -210,16 +212,19 @@
      *
      * A cell can have at most two text items and one image.
      * </p>
+     *
+     * @deprecated TO BE REMOVED; please use {@link GridRowBuilder.CellBuilder} instead.
      */
+    @Deprecated
     public static final class CellBuilder extends TemplateSliceBuilder {
-        private androidx.slice.builders.impl.GridBuilder.CellBuilder mImpl;
+        private androidx.slice.builders.impl.GridRowBuilder.CellBuilder mImpl;
 
         /**
          * Create a builder which will construct a slice displayed as a cell in a grid.
          * @param parent The builder constructing the parent slice.
          */
         public CellBuilder(@NonNull GridBuilder parent) {
-            super(parent.mImpl.createGridBuilder());
+            super(parent.mImpl.createGridRowBuilder());
         }
 
         /**
@@ -227,12 +232,12 @@
          * @param uri Uri to tag for this slice.
          */
         public CellBuilder(@NonNull GridBuilder parent, @NonNull Uri uri) {
-            super(parent.mImpl.createGridBuilder(uri));
+            super(parent.mImpl.createGridRowBuilder(uri));
         }
 
         @Override
         void setImpl(TemplateBuilderImpl impl) {
-            mImpl = (androidx.slice.builders.impl.GridBuilder.CellBuilder) impl;
+            mImpl = (androidx.slice.builders.impl.GridRowBuilder.CellBuilder) impl;
         }
 
         /**
diff --git a/slices/builders/src/main/java/androidx/slice/builders/GridRowBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
new file mode 100644
index 0000000..e8b8836
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 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 androidx.slice.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.slice.builders.impl.TemplateBuilderImpl;
+
+import java.util.function.Consumer;
+
+
+/**
+ * Builder to construct a row of slice content in a grid format.
+ * <p>
+ * A grid row is composed of cells, each cell can have a combination of text and images. For more
+ * details see {@link CellBuilder}.
+ * </p>
+ */
+public class GridRowBuilder extends TemplateSliceBuilder {
+
+    private androidx.slice.builders.impl.GridRowBuilder mImpl;
+    private boolean mHasSeeMore;
+
+    /**
+     * Create a builder which will construct a slice displayed in a grid format.
+     * @param parent The builder constructing the parent slice.
+     */
+    public GridRowBuilder(@NonNull ListBuilder parent) {
+        super(parent.getImpl().createGridBuilder());
+    }
+
+    @Override
+    void setImpl(TemplateBuilderImpl impl) {
+        mImpl = (androidx.slice.builders.impl.GridRowBuilder) impl;
+    }
+
+    /**
+     * Add a cell to the grid builder.
+     */
+    @NonNull
+    public GridRowBuilder addCell(@NonNull CellBuilder builder) {
+        mImpl.addCell((TemplateBuilderImpl) builder.mImpl);
+        return this;
+    }
+
+    /**
+     * Add a cell to the grid builder.
+     */
+    @RequiresApi(Build.VERSION_CODES.N)
+    @NonNull
+    public GridRowBuilder addCell(@NonNull Consumer<CellBuilder> c) {
+        CellBuilder b = new CellBuilder(this);
+        c.accept(b);
+        return addCell(b);
+    }
+
+    /**
+     * If all content in a slice cannot be shown, the cell added here may be displayed where the
+     * content is cut off.
+     * <p>
+     * This method should only be used if you want to display a custom cell to indicate more
+     * content, consider using {@link #addSeeMoreAction(PendingIntent)} otherwise. If you do
+     * choose to specify a custom cell, the cell should have
+     * {@link CellBuilder#setContentIntent(PendingIntent)} specified to take the user to an
+     * activity to see all of the content.
+     * </p>
+     * <p>
+     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
+     * a row or action has been previously added.
+     * </p>
+     */
+    @NonNull
+    public GridRowBuilder addSeeMoreCell(@NonNull CellBuilder builder) {
+        if (mHasSeeMore) {
+            throw new IllegalStateException("Trying to add see more cell when one has "
+                    + "already been added");
+        }
+        mImpl.addSeeMoreCell((TemplateBuilderImpl) builder.mImpl);
+        mHasSeeMore = true;
+        return this;
+    }
+
+    /**
+     * If all content in a slice cannot be shown, the cell added here may be displayed where the
+     * content is cut off.
+     * <p>
+     * This method should only be used if you want to display a custom cell to indicate more
+     * content, consider using {@link #addSeeMoreAction(PendingIntent)} otherwise. If you do
+     * choose to specify a custom cell, the cell should have
+     * {@link CellBuilder#setContentIntent(PendingIntent)} specified to take the user to an
+     * activity to see all of the content.
+     * </p>
+     * <p>
+     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
+     * a row or action has been previously added.
+     * </p>
+     */
+    @RequiresApi(Build.VERSION_CODES.N)
+    @NonNull
+    public GridRowBuilder addSeeMoreCell(@NonNull Consumer<CellBuilder> c) {
+        CellBuilder b = new CellBuilder(this);
+        c.accept(b);
+        return addSeeMoreCell(b);
+    }
+
+    /**
+     * If all content in a slice cannot be shown, a "see more" affordance may be displayed where
+     * the content is cut off. The action added here should take the user to an activity to see
+     * all of the content, and will be invoked when the "see more" affordance is tapped.
+     * <p>
+     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
+     * a row or action has been previously added.
+     * </p>
+     */
+    @NonNull
+    public GridRowBuilder addSeeMoreAction(@NonNull PendingIntent intent) {
+        if (mHasSeeMore) {
+            throw new IllegalStateException("Trying to add see more action when one has "
+                    + "already been added");
+        }
+        mImpl.addSeeMoreAction(intent);
+        mHasSeeMore = true;
+        return this;
+    }
+
+    /**
+     * Sets the intent to send when the slice is activated.
+     */
+    @NonNull
+    public GridRowBuilder setPrimaryAction(@NonNull SliceAction action) {
+        mImpl.setPrimaryAction(action);
+        return this;
+    }
+
+    /**
+     * Sets the content description for the entire grid row.
+     */
+    @NonNull
+    public GridRowBuilder setContentDescription(@NonNull CharSequence description) {
+        mImpl.setContentDescription(description);
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public androidx.slice.builders.impl.GridRowBuilder getImpl() {
+        return mImpl;
+    }
+
+    /**
+     * Sub-builder to construct a cell to be displayed in a grid.
+     * <p>
+     * Content added to a cell will be displayed in order vertically, for example the below code
+     * would construct a cell with "First text", and image below it, and then "Second text" below
+     * the image.
+     *
+     * <pre class="prettyprint">
+     * CellBuilder cb = new CellBuilder(parent, sliceUri);
+     * cb.addText("First text")
+     *   .addImage(middleIcon)
+     *   .addText("Second text");
+     * </pre>
+     *
+     * A cell can have at most two text items and one image.
+     * </p>
+     */
+    public static final class CellBuilder extends TemplateSliceBuilder {
+        private androidx.slice.builders.impl.GridRowBuilder.CellBuilder mImpl;
+
+        /**
+         * Create a builder which will construct a slice displayed as a cell in a grid.
+         * @param parent The builder constructing the parent slice.
+         */
+        public CellBuilder(@NonNull GridRowBuilder parent) {
+            super(parent.mImpl.createGridRowBuilder());
+        }
+
+        /**
+         * Create a builder which will construct a slice displayed as a cell in a grid.
+         * @param uri Uri to tag for this slice.
+         */
+        public CellBuilder(@NonNull GridRowBuilder parent, @NonNull Uri uri) {
+            super(parent.mImpl.createGridRowBuilder(uri));
+        }
+
+        @Override
+        void setImpl(TemplateBuilderImpl impl) {
+            mImpl = (androidx.slice.builders.impl.GridRowBuilder.CellBuilder) impl;
+        }
+
+        /**
+         * Adds text to the cell. There can be at most two text items, the first two added
+         * will be used, others will be ignored.
+         */
+        @NonNull
+        public CellBuilder addText(@NonNull CharSequence text) {
+            return addText(text, false /* isLoading */);
+        }
+
+        /**
+         * Adds text to the cell. There can be at most two text items, the first two added
+         * will be used, others will be ignored.
+         * <p>
+         * Use this method to specify content that will appear in the template once it's been
+         * loaded.
+         * </p>
+         * @param isLoading indicates whether the app is doing work to load the added content in the
+         *                  background or not.
+         */
+        @NonNull
+        public CellBuilder addText(@Nullable CharSequence text, boolean isLoading) {
+            mImpl.addText(text, isLoading);
+            return this;
+        }
+
+        /**
+         * Adds text to the cell. Text added with this method will be styled as a title.
+         * There can be at most two text items, the first two added will be used, others
+         * will be ignored.
+         */
+        @NonNull
+        public CellBuilder addTitleText(@NonNull CharSequence text) {
+            return addTitleText(text, false /* isLoading */);
+        }
+
+        /**
+         * Adds text to the cell. Text added with this method will be styled as a title.
+         * There can be at most two text items, the first two added will be used, others
+         * will be ignored.
+         * <p>
+         * Use this method to specify content that will appear in the template once it's been
+         * loaded.
+         * </p>
+         * @param isLoading indicates whether the app is doing work to load the added content in the
+         *                  background or not.
+         */
+        @NonNull
+        public CellBuilder addTitleText(@Nullable CharSequence text, boolean isLoading) {
+            mImpl.addTitleText(text, isLoading);
+            return this;
+        }
+
+        /**
+         * Adds an image to the cell. There can be at most one image, the first one added will be
+         * used, others will be ignored.
+         *
+         * @param image the image to display in the cell.
+         * @param imageMode the mode that image should be displayed in.
+         *
+         * @see ListBuilder#ICON_IMAGE
+         * @see ListBuilder#SMALL_IMAGE
+         * @see ListBuilder#LARGE_IMAGE
+         */
+        @NonNull
+        public CellBuilder addImage(@NonNull Icon image, @ListBuilder.ImageMode int imageMode) {
+            return addImage(image, imageMode, false /* isLoading */);
+        }
+
+        /**
+         * Adds an image to the cell. There can be at most one image, the first one added will be
+         * used, others will be ignored.
+         * <p>
+         * Use this method to specify content that will appear in the template once it's been
+         * loaded.
+         * </p>
+         * @param image the image to display in the cell.
+         * @param imageMode the mode that image should be displayed in.
+         * @param isLoading indicates whether the app is doing work to load the added content in the
+         *                  background or not.
+         *
+         * @see ListBuilder#ICON_IMAGE
+         * @see ListBuilder#SMALL_IMAGE
+         * @see ListBuilder#LARGE_IMAGE
+         */
+        @NonNull
+        public CellBuilder addImage(@Nullable Icon image, @ListBuilder.ImageMode int imageMode,
+                boolean isLoading) {
+            mImpl.addImage(image, imageMode, isLoading);
+            return this;
+        }
+
+        /**
+         * Sets the action to be invoked if the user taps on this cell in the row.
+         */
+        @NonNull
+        public CellBuilder setContentIntent(@NonNull PendingIntent intent) {
+            mImpl.setContentIntent(intent);
+            return this;
+        }
+
+        /**
+         * Sets the content description for this cell.
+         */
+        @NonNull
+        public CellBuilder setContentDescription(@NonNull CharSequence description) {
+            mImpl.setContentDescription(description);
+            return this;
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
index 781b2bd..bec2af9 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/ListBuilder.java
@@ -25,9 +25,6 @@
 import android.net.Uri;
 import android.os.Build;
 
-import java.util.List;
-import java.util.function.Consumer;
-
 import androidx.annotation.ColorInt;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -39,6 +36,9 @@
 import androidx.slice.builders.impl.ListBuilderV1Impl;
 import androidx.slice.builders.impl.TemplateBuilderImpl;
 
+import java.util.List;
+import java.util.function.Consumer;
+
 /**
  * Builder to construct slice content in a list format.
  * <p>
@@ -120,10 +120,36 @@
 
     /**
      * Add a grid row to the list builder.
+     *
+     * @deprecated TO BE REMOVED; use {@link #addGridRow(GridRowBuilder)} instead
      */
     @NonNull
+    @Deprecated
     public ListBuilder addGrid(@NonNull GridBuilder builder) {
-        mImpl.addGrid((TemplateBuilderImpl) builder.getImpl());
+        mImpl.addGridRow((TemplateBuilderImpl) builder.getImpl());
+        return this;
+    }
+
+    /**
+     * Add a grid row to the list builder.
+     *
+     * @deprecated TO BE REMOVED; use {@link #addGridRow(GridRowBuilder)} instead
+     */
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    @NonNull
+    @Deprecated
+    public ListBuilder addGrid(@NonNull Consumer<GridBuilder> c) {
+        GridBuilder b = new GridBuilder(this);
+        c.accept(b);
+        return addGrid(b);
+    }
+
+    /**
+     * Add a grid row to the list builder.
+     */
+    @NonNull
+    public ListBuilder addGridRow(@NonNull GridRowBuilder builder) {
+        mImpl.addGridRow((TemplateBuilderImpl) builder.getImpl());
         return this;
     }
 
@@ -132,10 +158,10 @@
      */
     @RequiresApi(api = Build.VERSION_CODES.N)
     @NonNull
-    public ListBuilder addGrid(@NonNull Consumer<GridBuilder> c) {
-        GridBuilder b = new GridBuilder(this);
+    public ListBuilder addGridRow(@NonNull Consumer<GridRowBuilder> c) {
+        GridRowBuilder b = new GridRowBuilder(this);
         c.accept(b);
-        return addGrid(b);
+        return addGridRow(b);
     }
 
     /**
diff --git a/slices/builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
index 0d4fa46..f3878be 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
@@ -20,19 +20,20 @@
 
 import android.content.Context;
 import android.net.Uri;
-import androidx.annotation.RestrictTo;
 import android.util.Log;
 import android.util.Pair;
 
-import java.util.Arrays;
-import java.util.List;
-
+import androidx.annotation.RestrictTo;
 import androidx.slice.Slice;
 import androidx.slice.SliceProvider;
 import androidx.slice.SliceSpec;
 import androidx.slice.SliceSpecs;
 import androidx.slice.builders.impl.TemplateBuilderImpl;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Base class of builders of various template types.
  */
@@ -131,7 +132,7 @@
 
     private List<SliceSpec> getSpecs() {
         if (SliceProvider.getCurrentSpecs() != null) {
-            return SliceProvider.getCurrentSpecs();
+            return new ArrayList<>(SliceProvider.getCurrentSpecs());
         }
         // TODO: Support getting specs from pinned info.
         Log.w(TAG, "Not currently bunding a slice");
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilder.java
deleted file mode 100644
index 99d5ce4..0000000
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilder.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright 2018 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 androidx.slice.builders.impl;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.app.PendingIntent;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.slice.builders.SliceAction;
-
-/**
- * @hide
- */
-@RestrictTo(LIBRARY)
-public interface GridBuilder {
-    /**
-     * Create an TemplateBuilderImpl that implements {@link CellBuilder}.
-     */
-    TemplateBuilderImpl createGridBuilder();
-
-    /**
-     * Create an TemplateBuilderImpl that implements {@link CellBuilder} with the specified Uri.
-     */
-    TemplateBuilderImpl createGridBuilder(Uri uri);
-
-    /**
-     * Add a cell to this builder. Expected to be a builder from {@link #createGridBuilder}.
-     */
-    void addCell(TemplateBuilderImpl impl);
-
-    /**
-     * If all content in a slice cannot be shown, the cell added here will be displayed where the
-     * content is cut off. This cell should have an affordance to take the user to an activity to
-     * see all of the content. Expected to be a builder from {@link #createGridBuilder}.
-     * <p>
-     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
-     * a row or action has been previously added.
-     * </p>
-     */
-    void addSeeMoreCell(TemplateBuilderImpl impl);
-
-    /**
-     * If all content in a slice cannot be shown, a "see more" affordance will be displayed where
-     * the content is cut off. The action added here should take the user to an activity to see
-     * all of the content, and will be invoked when the "see more" affordance is tapped.
-     * <p>
-     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
-     * a row or action has been previously added.
-     * </p>
-     */
-    void addSeeMoreAction(PendingIntent intent);
-
-    /**
-     * Sets the action to be invoked if the user taps on the main content of the template.
-     */
-    void setPrimaryAction(SliceAction action);
-
-    /**
-     * Sets the content description for the entire grid row.
-     */
-    void setContentDescription(CharSequence description);
-
-    /**
-     */
-    interface CellBuilder {
-        /**
-         * Adds text to the cell. There can be at most two text items, the first two added
-         * will be used, others will be ignored.
-         */
-        @NonNull
-        void addText(@NonNull CharSequence text);
-
-        /**
-         * Adds text to the cell. There can be at most two text items, the first two added
-         * will be used, others will be ignored.
-         * <p>
-         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
-         * to load this content in the background, in this case the template displays a placeholder
-         * until updated.
-         */
-        @NonNull
-        void addText(@Nullable CharSequence text, boolean isLoading);
-
-        /**
-         * Adds text to the cell. Text added with this method will be styled as a title.
-         * There can be at most two text items, the first two added will be used, others
-         * will be ignored.
-         */
-        @NonNull
-        void addTitleText(@NonNull CharSequence text);
-
-        /**
-         * Adds text to the cell. Text added with this method will be styled as a title.
-         * There can be at most two text items, the first two added will be used, others
-         * will be ignored.
-         * <p>
-         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
-         * to load this content in the background, in this case the template displays a placeholder
-         * until updated.
-         */
-        @NonNull
-        void addTitleText(@Nullable CharSequence text, boolean isLoading);
-
-        /**
-         * Adds an image to the cell. There can be at most one image, the first one added
-         * will be used, others will be ignored.
-         *
-         * @param image the image to display in the cell.
-         * @param imageMode the mode that image should be displayed in.
-         */
-        @NonNull
-        void addImage(@NonNull Icon image, int imageMode);
-
-        /**
-         * Adds an image to the cell. There can be at most one image, the first one added
-         * will be used, others will be ignored.
-         * <p>
-         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
-         * to load this content in the background, in this case the template displays a placeholder
-         * until updated.l.
-         */
-        @NonNull
-        void addImage(@NonNull Icon image, int imageMode, boolean isLoading);
-
-        /**
-         * Sets the action to be invoked if the user taps on this cell in the row.
-         */
-        @NonNull
-        void setContentIntent(@NonNull PendingIntent intent);
-
-        /**
-         * Sets the content description for this cell.
-         */
-        void setContentDescription(CharSequence description);
-    }
-}
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilderBasicImpl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilderBasicImpl.java
deleted file mode 100644
index 96df99e..0000000
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilderBasicImpl.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright 2018 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 androidx.slice.builders.impl;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.app.PendingIntent;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.slice.Slice;
-import androidx.slice.builders.SliceAction;
-
-
-/**
- * @hide
- */
-@RestrictTo(LIBRARY)
-public class GridBuilderBasicImpl extends TemplateBuilderImpl implements GridBuilder {
-
-    /**
-     */
-    public GridBuilderBasicImpl(@NonNull ListBuilderBasicImpl parent) {
-        super(parent.createChildBuilder(), null);
-    }
-
-    /**
-     */
-    @Override
-    public TemplateBuilderImpl createGridBuilder() {
-        return new CellBuilder(this);
-    }
-
-    /**
-     */
-    @Override
-    public TemplateBuilderImpl createGridBuilder(Uri uri) {
-        return new CellBuilder(uri);
-    }
-
-    /**
-     */
-    @Override
-    public void addCell(TemplateBuilderImpl impl) {
-        // TODO: Consider extracting some grid content for the basic version.
-    }
-
-    /**
-     */
-    @Override
-    public void addSeeMoreCell(TemplateBuilderImpl impl) {
-    }
-
-    /**
-     */
-    @Override
-    public void addSeeMoreAction(PendingIntent intent) {
-    }
-
-    /**
-     */
-    @Override
-    public void setPrimaryAction(SliceAction action) {
-    }
-
-    /**
-     */
-    @Override
-    public void setContentDescription(CharSequence description) {
-    }
-
-    /**
-     */
-    @Override
-    public void apply(Slice.Builder builder) {
-
-    }
-
-    /**
-     */
-    public static final class CellBuilder extends TemplateBuilderImpl implements
-            GridBuilder.CellBuilder {
-
-        /**
-         */
-        public CellBuilder(@NonNull GridBuilderBasicImpl parent) {
-            super(parent.createChildBuilder(), null);
-        }
-
-        /**
-         */
-        public CellBuilder(@NonNull Uri uri) {
-            super(new Slice.Builder(uri), null);
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addText(@NonNull CharSequence text) {
-        }
-
-        /**
-         */
-        @Override
-        public void addText(@Nullable CharSequence text, boolean isLoading) {
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addTitleText(@NonNull CharSequence text) {
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addTitleText(@Nullable CharSequence text, boolean isLoading) {
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addImage(@NonNull Icon image, int imageMode) {
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addImage(@Nullable Icon image, int imageMode, boolean isLoading) {
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void setContentIntent(@NonNull PendingIntent intent) {
-        }
-
-        /**
-         */
-        @Override
-        public void setContentDescription(CharSequence description) {
-        }
-
-        /**
-         */
-        @Override
-        public void apply(Slice.Builder builder) {
-
-        }
-    }
-}
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilderListV1Impl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilderListV1Impl.java
deleted file mode 100644
index 8dc89fe..0000000
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/GridBuilderListV1Impl.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright 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 androidx.slice.builders.impl;
-
-import static android.app.slice.Slice.HINT_HORIZONTAL;
-import static android.app.slice.Slice.HINT_LARGE;
-import static android.app.slice.Slice.HINT_LIST_ITEM;
-import static android.app.slice.Slice.HINT_NO_TINT;
-import static android.app.slice.Slice.HINT_PARTIAL;
-import static android.app.slice.Slice.HINT_SEE_MORE;
-import static android.app.slice.Slice.HINT_SHORTCUT;
-import static android.app.slice.Slice.HINT_TITLE;
-import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
-import static androidx.slice.builders.ListBuilder.LARGE_IMAGE;
-
-import android.app.PendingIntent;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-
-import java.util.ArrayList;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.slice.Slice;
-import androidx.slice.builders.SliceAction;
-
-/**
- * @hide
- */
-@RestrictTo(LIBRARY)
-public class GridBuilderListV1Impl extends TemplateBuilderImpl implements GridBuilder {
-
-    private SliceAction mPrimaryAction;
-
-    /**
-     */
-    public GridBuilderListV1Impl(@NonNull ListBuilderV1Impl parent) {
-        super(parent.createChildBuilder(), null);
-    }
-
-    /**
-     */
-    @Override
-    @NonNull
-    public Slice build() {
-        Slice.Builder sb = new Slice.Builder(getBuilder())
-                .addHints(HINT_HORIZONTAL, HINT_LIST_ITEM);
-        sb.addSubSlice(getBuilder().addHints(HINT_HORIZONTAL, HINT_LIST_ITEM).build());
-        if (mPrimaryAction != null) {
-            Slice.Builder actionBuilder = new Slice.Builder(getBuilder())
-                    .addHints(HINT_SHORTCUT, HINT_TITLE);
-            sb.addSubSlice(mPrimaryAction.buildSlice(actionBuilder));
-        }
-        return sb.build();
-    }
-
-    /**
-     */
-    @Override
-    public void apply(Slice.Builder builder) {
-    }
-
-    /**
-     */
-    @Override
-    public TemplateBuilderImpl createGridBuilder() {
-        return new CellBuilder(this);
-    }
-
-    /**
-     */
-    @Override
-    public TemplateBuilderImpl createGridBuilder(Uri uri) {
-        return new CellBuilder(uri);
-    }
-
-    /**
-     */
-    @Override
-    public void addCell(TemplateBuilderImpl builder) {
-        getBuilder().addSubSlice(builder.getBuilder().addHints(HINT_LIST_ITEM).build());
-    }
-
-
-    /**
-     */
-    @Override
-    public void addSeeMoreCell(@NonNull TemplateBuilderImpl builder) {
-        builder.getBuilder().addHints(HINT_SEE_MORE);
-        getBuilder().addSubSlice(builder.build());
-    }
-
-    /**
-     */
-    @Override
-    public void addSeeMoreAction(PendingIntent intent) {
-        getBuilder().addSubSlice(
-                new Slice.Builder(getBuilder())
-                        .addHints(HINT_SEE_MORE)
-                        .addAction(intent, new Slice.Builder(getBuilder()).build(), null)
-                        .build());
-    }
-
-    /**
-     */
-    @Override
-    public void setPrimaryAction(SliceAction action) {
-        mPrimaryAction = action;
-    }
-
-    /**
-     */
-    @Override
-    public void setContentDescription(CharSequence description) {
-        getBuilder().addText(description, SUBTYPE_CONTENT_DESCRIPTION);
-    }
-
-    /**
-     */
-    public static final class CellBuilder extends TemplateBuilderImpl implements
-            GridBuilder.CellBuilder {
-
-        private PendingIntent mContentIntent;
-
-        /**
-         */
-        public CellBuilder(@NonNull GridBuilderListV1Impl parent) {
-            super(parent.createChildBuilder(), null);
-        }
-
-        /**
-         */
-        public CellBuilder(@NonNull Uri uri) {
-            super(new Slice.Builder(uri), null);
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addText(@NonNull CharSequence text) {
-            addText(text, false /* isLoading */);
-        }
-
-        /**
-         */
-        @Override
-        public void addText(@Nullable CharSequence text, boolean isLoading) {
-            @Slice.SliceHint String[] hints = isLoading
-                    ? new String[] {HINT_PARTIAL}
-                    : new String[0];
-            getBuilder().addText(text, null, hints);
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addTitleText(@NonNull CharSequence text) {
-            addTitleText(text, false /* isLoading */);
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addTitleText(@Nullable CharSequence text, boolean isLoading) {
-            @Slice.SliceHint String[] hints = isLoading
-                    ? new String[] {HINT_PARTIAL, HINT_LARGE}
-                    : new String[] {HINT_LARGE};
-            getBuilder().addText(text, null, hints);
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addImage(@NonNull Icon image, int imageMode) {
-            addImage(image, imageMode, false /* isLoading */);
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void addImage(@Nullable Icon image, int imageMode, boolean isLoading) {
-            ArrayList<String> hints = new ArrayList<>();
-            if (imageMode != ICON_IMAGE) {
-                hints.add(HINT_NO_TINT);
-            }
-            if (imageMode == LARGE_IMAGE) {
-                hints.add(HINT_LARGE);
-            }
-            if (isLoading) {
-                hints.add(HINT_PARTIAL);
-            }
-            getBuilder().addIcon(image, null, hints);
-        }
-
-        /**
-         */
-        @NonNull
-        @Override
-        public void setContentIntent(@NonNull PendingIntent intent) {
-            mContentIntent = intent;
-        }
-
-        /**
-         */
-        @Override
-        public void setContentDescription(CharSequence description) {
-            getBuilder().addText(description, SUBTYPE_CONTENT_DESCRIPTION);
-        }
-
-        /**
-         * @hide
-         */
-        @RestrictTo(LIBRARY)
-        @Override
-        public void apply(Slice.Builder b) {
-        }
-
-        /**
-         */
-        @Override
-        @NonNull
-        public Slice build() {
-            if (mContentIntent != null) {
-                return new Slice.Builder(getBuilder())
-                        .addHints(HINT_HORIZONTAL)
-                        .addAction(mContentIntent, getBuilder().build(), null)
-                        .build();
-            }
-            return getBuilder().addHints(HINT_HORIZONTAL).build();
-        }
-    }
-}
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilder.java
new file mode 100644
index 0000000..b5eefff
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilder.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 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 androidx.slice.builders.impl;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.slice.builders.SliceAction;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public interface GridRowBuilder {
+    /**
+     * Create an TemplateBuilderImpl that implements {@link CellBuilder}.
+     */
+    TemplateBuilderImpl createGridRowBuilder();
+
+    /**
+     * Create an TemplateBuilderImpl that implements {@link CellBuilder} with the specified Uri.
+     */
+    TemplateBuilderImpl createGridRowBuilder(Uri uri);
+
+    /**
+     * Add a cell to this builder. Expected to be a builder from {@link #createGridRowBuilder}.
+     */
+    void addCell(TemplateBuilderImpl impl);
+
+    /**
+     * If all content in a slice cannot be shown, the cell added here will be displayed where the
+     * content is cut off. This cell should have an affordance to take the user to an activity to
+     * see all of the content. Expected to be a builder from {@link #createGridRowBuilder}.
+     * <p>
+     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
+     * a row or action has been previously added.
+     * </p>
+     */
+    void addSeeMoreCell(TemplateBuilderImpl impl);
+
+    /**
+     * If all content in a slice cannot be shown, a "see more" affordance will be displayed where
+     * the content is cut off. The action added here should take the user to an activity to see
+     * all of the content, and will be invoked when the "see more" affordance is tapped.
+     * <p>
+     * Only one see more affordance can be added, this throws {@link IllegalStateException} if
+     * a row or action has been previously added.
+     * </p>
+     */
+    void addSeeMoreAction(PendingIntent intent);
+
+    /**
+     * Sets the action to be invoked if the user taps on the main content of the template.
+     */
+    void setPrimaryAction(SliceAction action);
+
+    /**
+     * Sets the content description for the entire grid row.
+     */
+    void setContentDescription(CharSequence description);
+
+    /**
+     */
+    interface CellBuilder {
+        /**
+         * Adds text to the cell. There can be at most two text items, the first two added
+         * will be used, others will be ignored.
+         */
+        @NonNull
+        void addText(@NonNull CharSequence text);
+
+        /**
+         * Adds text to the cell. There can be at most two text items, the first two added
+         * will be used, others will be ignored.
+         * <p>
+         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
+         * to load this content in the background, in this case the template displays a placeholder
+         * until updated.
+         */
+        @NonNull
+        void addText(@Nullable CharSequence text, boolean isLoading);
+
+        /**
+         * Adds text to the cell. Text added with this method will be styled as a title.
+         * There can be at most two text items, the first two added will be used, others
+         * will be ignored.
+         */
+        @NonNull
+        void addTitleText(@NonNull CharSequence text);
+
+        /**
+         * Adds text to the cell. Text added with this method will be styled as a title.
+         * There can be at most two text items, the first two added will be used, others
+         * will be ignored.
+         * <p>
+         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
+         * to load this content in the background, in this case the template displays a placeholder
+         * until updated.
+         */
+        @NonNull
+        void addTitleText(@Nullable CharSequence text, boolean isLoading);
+
+        /**
+         * Adds an image to the cell. There can be at most one image, the first one added
+         * will be used, others will be ignored.
+         *
+         * @param image the image to display in the cell.
+         * @param imageMode the mode that image should be displayed in.
+         */
+        @NonNull
+        void addImage(@NonNull Icon image, int imageMode);
+
+        /**
+         * Adds an image to the cell. There can be at most one image, the first one added
+         * will be used, others will be ignored.
+         * <p>
+         * When set to true, the parameter {@code isLoading} indicates that the app is doing work
+         * to load this content in the background, in this case the template displays a placeholder
+         * until updated.l.
+         */
+        @NonNull
+        void addImage(@NonNull Icon image, int imageMode, boolean isLoading);
+
+        /**
+         * Sets the action to be invoked if the user taps on this cell in the row.
+         */
+        @NonNull
+        void setContentIntent(@NonNull PendingIntent intent);
+
+        /**
+         * Sets the content description for this cell.
+         */
+        void setContentDescription(CharSequence description);
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderBasicImpl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderBasicImpl.java
new file mode 100644
index 0000000..dcd8791
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderBasicImpl.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2018 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 androidx.slice.builders.impl;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.slice.Slice;
+import androidx.slice.builders.SliceAction;
+
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class GridRowBuilderBasicImpl extends TemplateBuilderImpl implements GridRowBuilder {
+
+    /**
+     */
+    public GridRowBuilderBasicImpl(@NonNull ListBuilderBasicImpl parent) {
+        super(parent.createChildBuilder(), null);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridRowBuilder() {
+        return new CellBuilder(this);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridRowBuilder(Uri uri) {
+        return new CellBuilder(uri);
+    }
+
+    /**
+     */
+    @Override
+    public void addCell(TemplateBuilderImpl impl) {
+        // TODO: Consider extracting some grid content for the basic version.
+    }
+
+    /**
+     */
+    @Override
+    public void addSeeMoreCell(TemplateBuilderImpl impl) {
+    }
+
+    /**
+     */
+    @Override
+    public void addSeeMoreAction(PendingIntent intent) {
+    }
+
+    /**
+     */
+    @Override
+    public void setPrimaryAction(SliceAction action) {
+    }
+
+    /**
+     */
+    @Override
+    public void setContentDescription(CharSequence description) {
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+
+    }
+
+    /**
+     */
+    public static final class CellBuilder extends TemplateBuilderImpl implements
+            GridRowBuilder.CellBuilder {
+
+        /**
+         */
+        public CellBuilder(@NonNull GridRowBuilderBasicImpl parent) {
+            super(parent.createChildBuilder(), null);
+        }
+
+        /**
+         */
+        public CellBuilder(@NonNull Uri uri) {
+            super(new Slice.Builder(uri), null);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addText(@NonNull CharSequence text) {
+        }
+
+        /**
+         */
+        @Override
+        public void addText(@Nullable CharSequence text, boolean isLoading) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addTitleText(@NonNull CharSequence text) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addTitleText(@Nullable CharSequence text, boolean isLoading) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addImage(@NonNull Icon image, int imageMode) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addImage(@Nullable Icon image, int imageMode, boolean isLoading) {
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setContentIntent(@NonNull PendingIntent intent) {
+        }
+
+        /**
+         */
+        @Override
+        public void setContentDescription(CharSequence description) {
+        }
+
+        /**
+         */
+        @Override
+        public void apply(Slice.Builder builder) {
+
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
new file mode 100644
index 0000000..0c077ba
--- /dev/null
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 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 androidx.slice.builders.impl;
+
+import static android.app.slice.Slice.HINT_HORIZONTAL;
+import static android.app.slice.Slice.HINT_LARGE;
+import static android.app.slice.Slice.HINT_LIST_ITEM;
+import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_PARTIAL;
+import static android.app.slice.Slice.HINT_SEE_MORE;
+import static android.app.slice.Slice.HINT_SHORTCUT;
+import static android.app.slice.Slice.HINT_TITLE;
+import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
+import static androidx.slice.builders.ListBuilder.LARGE_IMAGE;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.slice.Slice;
+import androidx.slice.builders.SliceAction;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class GridRowBuilderListV1Impl extends TemplateBuilderImpl implements GridRowBuilder {
+
+    private SliceAction mPrimaryAction;
+
+    /**
+     */
+    public GridRowBuilderListV1Impl(@NonNull ListBuilderV1Impl parent) {
+        super(parent.createChildBuilder(), null);
+    }
+
+    /**
+     */
+    @Override
+    @NonNull
+    public Slice build() {
+        Slice.Builder sb = new Slice.Builder(getBuilder())
+                .addHints(HINT_HORIZONTAL, HINT_LIST_ITEM);
+        sb.addSubSlice(getBuilder().addHints(HINT_HORIZONTAL, HINT_LIST_ITEM).build());
+        if (mPrimaryAction != null) {
+            Slice.Builder actionBuilder = new Slice.Builder(getBuilder())
+                    .addHints(HINT_SHORTCUT, HINT_TITLE);
+            sb.addSubSlice(mPrimaryAction.buildSlice(actionBuilder));
+        }
+        return sb.build();
+    }
+
+    /**
+     */
+    @Override
+    public void apply(Slice.Builder builder) {
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridRowBuilder() {
+        return new CellBuilder(this);
+    }
+
+    /**
+     */
+    @Override
+    public TemplateBuilderImpl createGridRowBuilder(Uri uri) {
+        return new CellBuilder(uri);
+    }
+
+    /**
+     */
+    @Override
+    public void addCell(TemplateBuilderImpl builder) {
+        getBuilder().addSubSlice(builder.getBuilder().addHints(HINT_LIST_ITEM).build());
+    }
+
+
+    /**
+     */
+    @Override
+    public void addSeeMoreCell(@NonNull TemplateBuilderImpl builder) {
+        builder.getBuilder().addHints(HINT_SEE_MORE);
+        getBuilder().addSubSlice(builder.build());
+    }
+
+    /**
+     */
+    @Override
+    public void addSeeMoreAction(PendingIntent intent) {
+        getBuilder().addSubSlice(
+                new Slice.Builder(getBuilder())
+                        .addHints(HINT_SEE_MORE)
+                        .addAction(intent, new Slice.Builder(getBuilder()).build(), null)
+                        .build());
+    }
+
+    /**
+     */
+    @Override
+    public void setPrimaryAction(SliceAction action) {
+        mPrimaryAction = action;
+    }
+
+    /**
+     */
+    @Override
+    public void setContentDescription(CharSequence description) {
+        getBuilder().addText(description, SUBTYPE_CONTENT_DESCRIPTION);
+    }
+
+    /**
+     */
+    public static final class CellBuilder extends TemplateBuilderImpl implements
+            GridRowBuilder.CellBuilder {
+
+        private PendingIntent mContentIntent;
+
+        /**
+         */
+        public CellBuilder(@NonNull GridRowBuilderListV1Impl parent) {
+            super(parent.createChildBuilder(), null);
+        }
+
+        /**
+         */
+        public CellBuilder(@NonNull Uri uri) {
+            super(new Slice.Builder(uri), null);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addText(@NonNull CharSequence text) {
+            addText(text, false /* isLoading */);
+        }
+
+        /**
+         */
+        @Override
+        public void addText(@Nullable CharSequence text, boolean isLoading) {
+            @Slice.SliceHint String[] hints = isLoading
+                    ? new String[] {HINT_PARTIAL}
+                    : new String[0];
+            getBuilder().addText(text, null, hints);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addTitleText(@NonNull CharSequence text) {
+            addTitleText(text, false /* isLoading */);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addTitleText(@Nullable CharSequence text, boolean isLoading) {
+            @Slice.SliceHint String[] hints = isLoading
+                    ? new String[] {HINT_PARTIAL, HINT_LARGE}
+                    : new String[] {HINT_LARGE};
+            getBuilder().addText(text, null, hints);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addImage(@NonNull Icon image, int imageMode) {
+            addImage(image, imageMode, false /* isLoading */);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void addImage(@Nullable Icon image, int imageMode, boolean isLoading) {
+            ArrayList<String> hints = new ArrayList<>();
+            if (imageMode != ICON_IMAGE) {
+                hints.add(HINT_NO_TINT);
+            }
+            if (imageMode == LARGE_IMAGE) {
+                hints.add(HINT_LARGE);
+            }
+            if (isLoading) {
+                hints.add(HINT_PARTIAL);
+            }
+            getBuilder().addIcon(image, null, hints);
+        }
+
+        /**
+         */
+        @NonNull
+        @Override
+        public void setContentIntent(@NonNull PendingIntent intent) {
+            mContentIntent = intent;
+        }
+
+        /**
+         */
+        @Override
+        public void setContentDescription(CharSequence description) {
+            getBuilder().addText(description, SUBTYPE_CONTENT_DESCRIPTION);
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY)
+        @Override
+        public void apply(Slice.Builder b) {
+        }
+
+        /**
+         */
+        @Override
+        @NonNull
+        public Slice build() {
+            if (mContentIntent != null) {
+                return new Slice.Builder(getBuilder())
+                        .addHints(HINT_HORIZONTAL)
+                        .addAction(mContentIntent, getBuilder().build(), null)
+                        .build();
+            }
+            return getBuilder().addHints(HINT_HORIZONTAL).build();
+        }
+    }
+}
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
index 386f215..da4fb69 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
@@ -22,13 +22,13 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 
-import java.util.List;
-
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.slice.builders.SliceAction;
 
+import java.util.List;
+
 /**
  * @hide
  */
@@ -42,7 +42,7 @@
     /**
      * Add a grid row to the list builder.
      */
-    void addGrid(TemplateBuilderImpl impl);
+    void addGridRow(TemplateBuilderImpl impl);
 
     /**
      * Adds a header to this template.
@@ -108,7 +108,7 @@
      */
     TemplateBuilderImpl createRowBuilder(Uri uri);
     /**
-     * Create a builder that implements {@link GridBuilder}.
+     * Create a builder that implements {@link GridRowBuilder}.
      */
     TemplateBuilderImpl createGridBuilder();
     /**
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
index 8da1d96..e9a659d 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
@@ -23,8 +23,6 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 
-import java.util.List;
-
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
@@ -32,6 +30,8 @@
 import androidx.slice.SliceSpec;
 import androidx.slice.builders.SliceAction;
 
+import java.util.List;
+
 /**
  * @hide
  */
@@ -54,7 +54,7 @@
     /**
      */
     @Override
-    public void addGrid(TemplateBuilderImpl impl) {
+    public void addGridRow(TemplateBuilderImpl impl) {
         // Do nothing.
     }
 
@@ -129,7 +129,7 @@
      */
     @Override
     public TemplateBuilderImpl createGridBuilder() {
-        return new GridBuilderBasicImpl(this);
+        return new GridRowBuilderBasicImpl(this);
     }
 
     @Override
diff --git a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
index a9be556..6671024 100644
--- a/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
+++ b/slices/builders/src/main/java/androidx/slice/builders/impl/ListBuilderV1Impl.java
@@ -41,9 +41,6 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
@@ -52,6 +49,9 @@
 import androidx.slice.SliceSpec;
 import androidx.slice.builders.SliceAction;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * @hide
  */
@@ -96,7 +96,7 @@
      */
     @NonNull
     @Override
-    public void addGrid(@NonNull TemplateBuilderImpl builder) {
+    public void addGridRow(@NonNull TemplateBuilderImpl builder) {
         getBuilder().addSubSlice(builder.build());
     }
 
@@ -280,7 +280,7 @@
      */
     @Override
     public TemplateBuilderImpl createGridBuilder() {
-        return new GridBuilderListV1Impl(this);
+        return new GridRowBuilderListV1Impl(this);
     }
 
     /**
diff --git a/slices/core/src/androidTest/java/androidx/slice/SliceTest.java b/slices/core/src/androidTest/java/androidx/slice/SliceTest.java
index ef3b000..563473a 100644
--- a/slices/core/src/androidTest/java/androidx/slice/SliceTest.java
+++ b/slices/core/src/androidTest/java/androidx/slice/SliceTest.java
@@ -44,6 +44,8 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import androidx.slice.core.test.R;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -52,8 +54,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import androidx.slice.core.test.R;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SliceTest {
@@ -68,7 +68,7 @@
         sFlag = false;
         Slice.bindSlice(mContext,
                 BASE_URI.buildUpon().appendPath("set_flag").build(),
-                Collections.<SliceSpec>emptyList());
+                Collections.<SliceSpec>emptySet());
         assertFalse(sFlag);
     }
 
@@ -79,14 +79,14 @@
 
     @Test
     public void testSliceUri() {
-        Slice s = Slice.bindSlice(mContext, BASE_URI, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, BASE_URI, Collections.<SliceSpec>emptySet());
         assertEquals(BASE_URI, s.getUri());
     }
 
     @Test
     public void testSubSlice() {
         Uri uri = BASE_URI.buildUpon().appendPath("subslice").build();
-        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
         assertEquals(uri, s.getUri());
         assertEquals(1, s.getItems().size());
 
@@ -101,7 +101,7 @@
     @Test
     public void testText() {
         Uri uri = BASE_URI.buildUpon().appendPath("text").build();
-        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
         assertEquals(uri, s.getUri());
         assertEquals(1, s.getItems().size());
 
@@ -114,7 +114,7 @@
     @Test
     public void testIcon() {
         Uri uri = BASE_URI.buildUpon().appendPath("icon").build();
-        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
         assertEquals(uri, s.getUri());
         assertEquals(1, s.getItems().size());
 
@@ -138,7 +138,7 @@
         mContext.registerReceiver(receiver,
                 new IntentFilter(mContext.getPackageName() + ".action"));
         Uri uri = BASE_URI.buildUpon().appendPath("action").build();
-        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
         assertEquals(uri, s.getUri());
         assertEquals(1, s.getItems().size());
 
@@ -161,7 +161,7 @@
     @Test
     public void testInt() {
         Uri uri = BASE_URI.buildUpon().appendPath("int").build();
-        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
         assertEquals(uri, s.getUri());
         assertEquals(1, s.getItems().size());
 
@@ -173,7 +173,7 @@
     @Test
     public void testTimestamp() {
         Uri uri = BASE_URI.buildUpon().appendPath("timestamp").build();
-        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
         assertEquals(uri, s.getUri());
         assertEquals(1, s.getItems().size());
 
@@ -187,7 +187,7 @@
         // Note this tests that hints are propagated through to the client but not that any specific
         // hints have any effects.
         Uri uri = BASE_URI.buildUpon().appendPath("hints").build();
-        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptyList());
+        Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
         assertEquals(uri, s.getUri());
 
         assertEquals(Arrays.asList(HINT_LIST), s.getHints());
diff --git a/slices/core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java b/slices/core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java
index 939309d..7a37396 100644
--- a/slices/core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java
+++ b/slices/core/src/androidTest/java/androidx/slice/compat/CompatPinnedListTest.java
@@ -22,14 +22,17 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assert.assertArrayEquals;
-
 import android.content.Context;
 import android.net.Uri;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import androidx.collection.ArraySet;
+import androidx.slice.SliceSpec;
+
+import junit.framework.AssertionFailedError;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -37,9 +40,7 @@
 
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
-
-import androidx.slice.SliceSpec;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -47,7 +48,7 @@
 
     private final Context mContext = InstrumentationRegistry.getContext();
     private CompatPinnedList mCompatPinnedList;
-    private List<SliceSpec> mSpecs;
+    private Set<SliceSpec> mSpecs;
 
     private static final SliceSpec[] FIRST_SPECS = new SliceSpec[]{
             new SliceSpec("spec1", 3),
@@ -66,7 +67,7 @@
     @Before
     public void setup() {
         mCompatPinnedList = new CompatPinnedList(mContext, "test_file");
-        mSpecs = Collections.emptyList();
+        mSpecs = Collections.emptySet();
     }
 
     @After
@@ -110,19 +111,31 @@
     public void testMergeSpecs() {
         Uri uri = Uri.parse("content://something/something");
 
-        assertEquals(Collections.emptyList(), mCompatPinnedList.getSpecs(uri));
+        assertEquals(Collections.emptySet(), mCompatPinnedList.getSpecs(uri));
 
-        mCompatPinnedList.addPin(uri, "my_pkg", Arrays.asList(FIRST_SPECS));
-        assertArrayEquals(FIRST_SPECS, mCompatPinnedList.getSpecs(uri).toArray(new SliceSpec[0]));
+        mCompatPinnedList.addPin(uri, "my_pkg", new ArraySet<>(Arrays.asList(FIRST_SPECS)));
+        assertSetEquals(new ArraySet<>(Arrays.asList(FIRST_SPECS)),
+                mCompatPinnedList.getSpecs(uri));
 
-        mCompatPinnedList.addPin(uri, "my_pkg2", Arrays.asList(SECOND_SPECS));
-        assertArrayEquals(new SliceSpec[]{
+        mCompatPinnedList.addPin(uri, "my_pkg2", new ArraySet<>(Arrays.asList(SECOND_SPECS)));
+        assertSetEquals(new ArraySet<>(Arrays.asList(new SliceSpec[]{
                 // spec1 is gone because it's not in the second set.
                 new SliceSpec("spec2", 1), // spec2 is 1 because it's smaller in the second set.
                 new SliceSpec("spec3", 2), // spec3 is the same in both sets
                 new SliceSpec("spec4", 1), // spec4 is 1 because it's smaller in the first set.
                 // spec5 is gone because it's not in the first set.
-        }, mCompatPinnedList.getSpecs(uri).toArray(new SliceSpec[0]));
+        })), mCompatPinnedList.getSpecs(uri));
+    }
 
+    private <T> void assertSetEquals(Set<T> a, Set<T> b) {
+        if (a.size() != b.size()) {
+            throw new AssertionFailedError("Wanted " + a + " but received " + b);
+        }
+
+        for (T o : a) {
+            if (!b.contains(o)) {
+                throw new AssertionFailedError("Wanted " + a + " but received " + b);
+            }
+        }
     }
 }
diff --git a/slices/core/src/main/java/androidx/slice/Slice.java b/slices/core/src/main/java/androidx/slice/Slice.java
index 2007e7a..d27d246 100644
--- a/slices/core/src/main/java/androidx/slice/Slice.java
+++ b/slices/core/src/main/java/androidx/slice/Slice.java
@@ -48,10 +48,6 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -61,6 +57,11 @@
 import androidx.core.os.BuildCompat;
 import androidx.slice.compat.SliceProviderCompat;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
 /**
  * A slice is a piece of app content and actions that can be surfaced outside of the app.
  *
@@ -441,7 +442,7 @@
     @SuppressWarnings("NewApi") // Lint doesn't understand BuildCompat.
     @Nullable
     public static Slice bindSlice(Context context, @NonNull Uri uri,
-            List<SliceSpec> supportedSpecs) {
+            Set<SliceSpec> supportedSpecs) {
         if (BuildCompat.isAtLeastP()) {
             return callBindSlice(context, uri, supportedSpecs);
         } else {
@@ -451,8 +452,8 @@
 
     @RequiresApi(28)
     private static Slice callBindSlice(Context context, Uri uri,
-            List<SliceSpec> supportedSpecs) {
+            Set<SliceSpec> supportedSpecs) {
         return SliceConvert.wrap(context.getSystemService(SliceManager.class)
-                .bindSlice(uri, unwrap(supportedSpecs)));
+                .bindSlice(uri, new ArrayList<>(unwrap(supportedSpecs))));
     }
 }
diff --git a/slices/core/src/main/java/androidx/slice/SliceConvert.java b/slices/core/src/main/java/androidx/slice/SliceConvert.java
index 542da98..58531fa 100644
--- a/slices/core/src/main/java/androidx/slice/SliceConvert.java
+++ b/slices/core/src/main/java/androidx/slice/SliceConvert.java
@@ -26,9 +26,10 @@
 
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
+import androidx.collection.ArraySet;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Convert between {@link androidx.slice.Slice} and {@link android.app.slice.Slice}
@@ -78,9 +79,9 @@
         return new android.app.slice.SliceSpec(spec.getType(), spec.getRevision());
     }
 
-    static List<android.app.slice.SliceSpec> unwrap(
-            List<androidx.slice.SliceSpec> supportedSpecs) {
-        List<android.app.slice.SliceSpec> ret = new ArrayList<>();
+    static Set<android.app.slice.SliceSpec> unwrap(
+            Set<androidx.slice.SliceSpec> supportedSpecs) {
+        Set<android.app.slice.SliceSpec> ret = new ArraySet<>();
         for (androidx.slice.SliceSpec spec : supportedSpecs) {
             ret.add(unwrap(spec));
         }
@@ -133,9 +134,9 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public static List<androidx.slice.SliceSpec> wrap(
+    public static Set<androidx.slice.SliceSpec> wrap(
             List<android.app.slice.SliceSpec> supportedSpecs) {
-        List<androidx.slice.SliceSpec> ret = new ArrayList<>();
+        Set<androidx.slice.SliceSpec> ret = new ArraySet<>();
         for (android.app.slice.SliceSpec spec : supportedSpecs) {
             ret.add(wrap(spec));
         }
diff --git a/slices/core/src/main/java/androidx/slice/SliceProvider.java b/slices/core/src/main/java/androidx/slice/SliceProvider.java
index b0cd22f..51ffea6 100644
--- a/slices/core/src/main/java/androidx/slice/SliceProvider.java
+++ b/slices/core/src/main/java/androidx/slice/SliceProvider.java
@@ -23,16 +23,16 @@
 import android.content.pm.ProviderInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.core.os.BuildCompat;
-
-import java.util.List;
-
 import androidx.slice.compat.ContentProviderWrapper;
 import androidx.slice.compat.SliceProviderCompat;
 import androidx.slice.compat.SliceProviderWrapperContainer;
 
+import java.util.Set;
+
 /**
  * A SliceProvider allows an app to provide content to be displayed in system spaces. This content
  * is templated and can contain actions, and the behavior of how it is surfaced is specific to the
@@ -74,7 +74,7 @@
  */
 public abstract class SliceProvider extends ContentProviderWrapper {
 
-    private static List<SliceSpec> sSpecs;
+    private static Set<SliceSpec> sSpecs;
 
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
@@ -105,12 +105,12 @@
     public abstract boolean onCreateSliceProvider();
 
     /**
-     * Implemented to create a slice. Will be called on the main thread.
+     * Implemented to create a slice.
      * <p>
      * onBindSlice should return as quickly as possible so that the UI tied
      * to this slice can be responsive. No network or other IO will be allowed
      * during onBindSlice. Any loading that needs to be done should happen
-     * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+     * in the background with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
      * when the app is ready to provide the complete data in onBindSlice.
      * <p>
      *
@@ -169,7 +169,7 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public static void setSpecs(List<SliceSpec> specs) {
+    public static void setSpecs(Set<SliceSpec> specs) {
         sSpecs = specs;
     }
 
@@ -177,7 +177,7 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static List<SliceSpec> getCurrentSpecs() {
+    public static Set<SliceSpec> getCurrentSpecs() {
         return sSpecs;
     }
 }
diff --git a/slices/core/src/main/java/androidx/slice/SliceSpec.java b/slices/core/src/main/java/androidx/slice/SliceSpec.java
index 7f97769..27a4dfb 100644
--- a/slices/core/src/main/java/androidx/slice/SliceSpec.java
+++ b/slices/core/src/main/java/androidx/slice/SliceSpec.java
@@ -17,6 +17,7 @@
 package androidx.slice;
 
 import android.net.Uri;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 
@@ -84,4 +85,14 @@
         SliceSpec other = (SliceSpec) obj;
         return mType.equals(other.mType) && mRevision == other.mRevision;
     }
+
+    @Override
+    public int hashCode() {
+        return mType.hashCode() + Integer.hashCode(mRevision);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("SliceSpec{%s,%d}", mType, mRevision);
+    }
 }
diff --git a/slices/core/src/main/java/androidx/slice/compat/CompatPinnedList.java b/slices/core/src/main/java/androidx/slice/compat/CompatPinnedList.java
index c8e9032..56b8122 100644
--- a/slices/core/src/main/java/androidx/slice/compat/CompatPinnedList.java
+++ b/slices/core/src/main/java/androidx/slice/compat/CompatPinnedList.java
@@ -20,19 +20,16 @@
 import android.content.SharedPreferences;
 import android.net.Uri;
 import android.os.SystemClock;
+import android.text.TextUtils;
+
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArraySet;
 import androidx.core.util.ObjectsCompat;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
 import androidx.slice.SliceSpec;
 
+import java.util.Set;
+
 /**
  * Tracks the current packages requesting pinning of any given slice. It will clear the
  * list after a reboot since the packages are no longer requesting pinning.
@@ -80,18 +77,18 @@
     /**
      * Get the list of specs for a pinned Uri.
      */
-    public synchronized List<SliceSpec> getSpecs(Uri uri) {
-        List<SliceSpec> specs = new ArrayList<>();
+    public synchronized ArraySet<SliceSpec> getSpecs(Uri uri) {
+        ArraySet<SliceSpec> specs = new ArraySet<>();
         SharedPreferences prefs = getPrefs();
         String specNamesStr = prefs.getString(SPEC_NAME_PREFIX + uri.toString(), null);
         String specRevsStr = prefs.getString(SPEC_REV_PREFIX + uri.toString(), null);
         if (TextUtils.isEmpty(specNamesStr) || TextUtils.isEmpty(specRevsStr)) {
-            return Collections.emptyList();
+            return new ArraySet<>();
         }
         String[] specNames = specNamesStr.split(",");
         String[] specRevs = specRevsStr.split(",");
         if (specNames.length != specRevs.length) {
-            return Collections.emptyList();
+            return new ArraySet<>();
         }
         for (int i = 0; i < specNames.length; i++) {
             specs.add(new SliceSpec(specNames[i], Integer.parseInt(specRevs[i])));
@@ -105,12 +102,12 @@
                 .commit();
     }
 
-    private void setSpecs(Uri uri, List<SliceSpec> specs) {
+    private void setSpecs(Uri uri, ArraySet<SliceSpec> specs) {
         String[] specNames = new String[specs.size()];
         String[] specRevs = new String[specs.size()];
         for (int i = 0; i < specs.size(); i++) {
-            specNames[i] = specs.get(i).getType();
-            specRevs[i] = String.valueOf(specs.get(i).getRevision());
+            specNames[i] = specs.valueAt(i).getType();
+            specRevs[i] = String.valueOf(specs.valueAt(i).getRevision());
         }
         getPrefs().edit()
                 .putString(SPEC_NAME_PREFIX + uri.toString(), TextUtils.join(",", specNames))
@@ -127,13 +124,13 @@
      * Adds a pin for a specific uri/pkg pair and returns true if the
      * uri was not previously pinned.
      */
-    public synchronized boolean addPin(Uri uri, String pkg, List<SliceSpec> specs) {
+    public synchronized boolean addPin(Uri uri, String pkg, Set<SliceSpec> specs) {
         Set<String> pins = getPins(uri);
         boolean wasNotPinned = pins.isEmpty();
         pins.add(pkg);
         setPins(uri, pins);
         if (wasNotPinned) {
-            setSpecs(uri, specs);
+            setSpecs(uri, new ArraySet<>(specs));
         } else {
             setSpecs(uri, mergeSpecs(getSpecs(uri), specs));
         }
@@ -154,21 +151,22 @@
         return pins.size() == 0;
     }
 
-    private static List<SliceSpec> mergeSpecs(List<SliceSpec> specs,
-            List<SliceSpec> supportedSpecs) {
+    private static ArraySet<SliceSpec> mergeSpecs(ArraySet<SliceSpec> specs,
+            Set<SliceSpec> supportedSpecs) {
         for (int i = 0; i < specs.size(); i++) {
-            SliceSpec s = specs.get(i);
+            SliceSpec s = specs.valueAt(i);
             SliceSpec other = findSpec(supportedSpecs, s.getType());
             if (other == null) {
-                specs.remove(i--);
+                specs.removeAt(i--);
             } else if (other.getRevision() < s.getRevision()) {
-                specs.set(i, other);
+                specs.removeAt(i--);
+                specs.add(other);
             }
         }
         return specs;
     }
 
-    private static SliceSpec findSpec(List<SliceSpec> specs, String type) {
+    private static SliceSpec findSpec(Set<SliceSpec> specs, String type) {
         for (SliceSpec spec : specs) {
             if (ObjectsCompat.equals(spec.getType(), type)) {
                 return spec;
diff --git a/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java b/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
index 1c3e432..07bce69 100644
--- a/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
+++ b/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
@@ -40,22 +40,22 @@
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
-import androidx.annotation.Nullable;
+import android.util.ArraySet;
+import android.util.Log;
+
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.core.util.Preconditions;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
 import androidx.slice.Slice;
 import androidx.slice.SliceProvider;
 import androidx.slice.SliceSpec;
 import androidx.slice.core.R;
 import androidx.slice.core.SliceHints;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
 /**
  * @hide
  */
@@ -80,26 +80,19 @@
     public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
     private static final String DATA_PREFIX = "slice_data_";
 
+    private static final long SLICE_BIND_ANR = 2000;
+
     private static final boolean DEBUG = false;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private SliceProvider mSliceProvider;
     private CompatPinnedList mPinnedList;
-    private String mBindingPkg;
+
+    private String mCallback;
 
     public SliceProviderCompat(SliceProvider provider) {
         mSliceProvider = provider;
     }
 
-    /**
-     * Return the package name of the caller that initiated the binding request
-     * currently happening. The returned package will have been
-     * verified to belong to the calling UID. Returns {@code null} if not
-     * currently performing an {@link SliceProvider#onBindSlice(Uri)}.
-     */
-    public final @Nullable String getBindingPackage() {
-        return mBindingPkg;
-    }
-
     @Override
     public boolean onCreate() {
         mPinnedList = new CompatPinnedList(getContext(),
@@ -163,7 +156,7 @@
                         Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                         "Slice binding requires the permission BIND_SLICE");
             }
-            List<SliceSpec> specs = getSpecs(extras);
+            Set<SliceSpec> specs = getSpecs(extras);
 
             Slice s = handleBindSlice(uri, specs, getCallingPackage());
             Bundle b = new Bundle();
@@ -174,7 +167,7 @@
             Uri uri = mSliceProvider.onMapIntentToUri(intent);
             Bundle b = new Bundle();
             if (uri != null) {
-                List<SliceSpec> specs = getSpecs(extras);
+                Set<SliceSpec> specs = getSpecs(extras);
                 Slice s = handleBindSlice(uri, specs, getCallingPackage());
                 b.putParcelable(EXTRA_SLICE, s.toBundle());
             } else {
@@ -189,7 +182,7 @@
             return b;
         } else if (method.equals(METHOD_PIN)) {
             Uri uri = extras.getParcelable(EXTRA_BIND_URI);
-            List<SliceSpec> specs = getSpecs(extras);
+            Set<SliceSpec> specs = getSpecs(extras);
             String pkg = extras.getString(EXTRA_PKG);
             if (mPinnedList.addPin(uri, pkg, specs)) {
                 handleSlicePinned(uri);
@@ -212,46 +205,26 @@
     }
 
     private void handleSlicePinned(final Uri sliceUri) {
-        if (Looper.myLooper() == Looper.getMainLooper()) {
+        mCallback = "onSlicePinned";
+        mHandler.postDelayed(mAnr, SLICE_BIND_ANR);
+        try {
             mSliceProvider.onSlicePinned(sliceUri);
-        } else {
-            final CountDownLatch latch = new CountDownLatch(1);
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mSliceProvider.onSlicePinned(sliceUri);
-                    latch.countDown();
-                }
-            });
-            try {
-                latch.await();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
+        } finally {
+            mHandler.removeCallbacks(mAnr);
         }
     }
 
     private void handleSliceUnpinned(final Uri sliceUri) {
-        if (Looper.myLooper() == Looper.getMainLooper()) {
+        mCallback = "onSliceUnpinned";
+        mHandler.postDelayed(mAnr, SLICE_BIND_ANR);
+        try {
             mSliceProvider.onSliceUnpinned(sliceUri);
-        } else {
-            final CountDownLatch latch = new CountDownLatch(1);
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mSliceProvider.onSliceUnpinned(sliceUri);
-                    latch.countDown();
-                }
-            });
-            try {
-                latch.await();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
+        } finally {
+            mHandler.removeCallbacks(mAnr);
         }
     }
 
-    private Slice handleBindSlice(final Uri sliceUri, final List<SliceSpec> specs,
+    private Slice handleBindSlice(final Uri sliceUri, final Set<SliceSpec> specs,
             final String callingPkg) {
         // This can be removed once Slice#bindSlice is removed and everyone is using
         // SliceManager#bindSlice.
@@ -267,25 +240,7 @@
                 return createPermissionSlice(getContext(), sliceUri, pkg);
             }
         }
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            return onBindSliceStrict(sliceUri, specs, callingPkg);
-        } else {
-            final CountDownLatch latch = new CountDownLatch(1);
-            final Slice[] output = new Slice[1];
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    output[0] = onBindSliceStrict(sliceUri, specs, callingPkg);
-                    latch.countDown();
-                }
-            });
-            try {
-                latch.await();
-                return output[0];
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
+        return onBindSliceStrict(sliceUri, specs);
     }
 
     /**
@@ -335,8 +290,10 @@
         }
     }
 
-    private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> specs, String callingPackage) {
+    private Slice onBindSliceStrict(Uri sliceUri, Set<SliceSpec> specs) {
         ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+        mCallback = "onBindSlice";
+        mHandler.postDelayed(mAnr, SLICE_BIND_ANR);
         try {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectAll()
@@ -344,11 +301,10 @@
                     .build());
             SliceProvider.setSpecs(specs);
             try {
-                mBindingPkg = callingPackage;
                 return mSliceProvider.onBindSlice(sliceUri);
             } finally {
-                mBindingPkg = null;
                 SliceProvider.setSpecs(null);
+                mHandler.removeCallbacks(mAnr);
             }
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
@@ -359,7 +315,7 @@
      * Compat version of {@link Slice#bindSlice}.
      */
     public static Slice bindSlice(Context context, Uri uri,
-            List<SliceSpec> supportedSpecs) {
+            Set<SliceSpec> supportedSpecs) {
         ContentProviderClient provider = context.getContentResolver()
                 .acquireContentProviderClient(uri);
         if (provider == null) {
@@ -387,7 +343,7 @@
         }
     }
 
-    private static void addSpecs(Bundle extras, List<SliceSpec> supportedSpecs) {
+    private static void addSpecs(Bundle extras, Set<SliceSpec> supportedSpecs) {
         ArrayList<String> types = new ArrayList<>();
         ArrayList<Integer> revs = new ArrayList<>();
         for (SliceSpec spec : supportedSpecs) {
@@ -398,8 +354,8 @@
         extras.putIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS, revs);
     }
 
-    private static List<SliceSpec> getSpecs(Bundle extras) {
-        ArrayList<SliceSpec> specs = new ArrayList<>();
+    private static Set<SliceSpec> getSpecs(Bundle extras) {
+        ArraySet<SliceSpec> specs = new ArraySet<>();
         ArrayList<String> types = extras.getStringArrayList(EXTRA_SUPPORTED_SPECS);
         ArrayList<Integer> revs = extras.getIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS);
         for (int i = 0; i < types.size(); i++) {
@@ -412,7 +368,7 @@
      * Compat version of {@link Slice#bindSlice}.
      */
     public static Slice bindSlice(Context context, Intent intent,
-            List<SliceSpec> supportedSpecs) {
+            Set<SliceSpec> supportedSpecs) {
         ContentResolver resolver = context.getContentResolver();
 
         // Check if the intent has data for the slice uri on it and use that
@@ -459,7 +415,7 @@
      * Compat version of {@link android.app.slice.SliceManager#pinSlice}.
      */
     public static void pinSlice(Context context, Uri uri,
-            List<SliceSpec> supportedSpecs) {
+            Set<SliceSpec> supportedSpecs) {
         ContentProviderClient provider = context.getContentResolver()
                 .acquireContentProviderClient(uri);
         if (provider == null) {
@@ -483,7 +439,7 @@
      * Compat version of {@link android.app.slice.SliceManager#unpinSlice}.
      */
     public static void unpinSlice(Context context, Uri uri,
-            List<SliceSpec> supportedSpecs) {
+            Set<SliceSpec> supportedSpecs) {
         ContentProviderClient provider = context.getContentResolver()
                 .acquireContentProviderClient(uri);
         if (provider == null) {
@@ -506,7 +462,7 @@
     /**
      * Compat version of {@link android.app.slice.SliceManager#getPinnedSpecs(Uri)}.
      */
-    public static List<SliceSpec> getPinnedSpecs(Context context, Uri uri) {
+    public static Set<SliceSpec> getPinnedSpecs(Context context, Uri uri) {
         ContentProviderClient provider = context.getContentResolver()
                 .acquireContentProviderClient(uri);
         if (provider == null) {
@@ -578,4 +534,12 @@
             return null;
         }
     }
+
+    private final Runnable mAnr = new Runnable() {
+        @Override
+        public void run() {
+            Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT);
+            Log.wtf(TAG, "Timed out while handling slice callback " + mCallback);
+        }
+    };
 }
diff --git a/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java b/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
index e6765f6..65505f7 100644
--- a/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
+++ b/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
@@ -24,13 +24,14 @@
 import android.app.slice.SliceSpec;
 import android.content.Intent;
 import android.net.Uri;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.collection.ArraySet;
+import androidx.slice.SliceConvert;
 
 import java.util.List;
 
-import androidx.slice.SliceConvert;
-
 /**
  * @hide
  */
@@ -55,7 +56,7 @@
 
         @Override
         public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedVersions) {
-            androidx.slice.SliceProvider.setSpecs(wrap(supportedVersions));
+            androidx.slice.SliceProvider.setSpecs(new ArraySet<>(wrap(supportedVersions)));
             try {
                 return SliceConvert.unwrap(mSliceProvider.onBindSlice(sliceUri));
             } finally {
diff --git a/slices/view/api/current.txt b/slices/view/api/current.txt
index f9c305c..c176d3e 100644
--- a/slices/view/api/current.txt
+++ b/slices/view/api/current.txt
@@ -69,11 +69,11 @@
 
   public final class SliceLiveData {
     ctor public SliceLiveData();
-    method public static android.arch.lifecycle.LiveData<androidx.slice.Slice> fromIntent(android.content.Context, android.content.Intent);
-    method public static android.arch.lifecycle.LiveData<androidx.slice.Slice> fromUri(android.content.Context, android.net.Uri);
+    method public static androidx.lifecycle.LiveData<androidx.slice.Slice> fromIntent(android.content.Context, android.content.Intent);
+    method public static androidx.lifecycle.LiveData<androidx.slice.Slice> fromUri(android.content.Context, android.net.Uri);
   }
 
-  public class SliceView extends android.view.ViewGroup implements android.arch.lifecycle.Observer {
+  public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer {
     ctor public SliceView(android.content.Context);
     ctor public SliceView(android.content.Context, android.util.AttributeSet);
     ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
diff --git a/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java b/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java
index aa60b38..2ea8071 100644
--- a/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java
+++ b/slices/view/src/androidTest/java/androidx/slice/render/SliceCreator.java
@@ -35,7 +35,7 @@
 import android.text.style.ForegroundColorSpan;
 
 import androidx.slice.Slice;
-import androidx.slice.builders.GridBuilder;
+import androidx.slice.builders.GridRowBuilder;
 import androidx.slice.builders.ListBuilder;
 import androidx.slice.builders.MessagingSliceBuilder;
 import androidx.slice.builders.SliceAction;
@@ -117,69 +117,69 @@
                 "open weather app"), Icon.createWithResource(getContext(), R.drawable.weather_1),
                 "Weather is happening!");
         ListBuilder b = new ListBuilder(getContext(), sliceUri);
-        GridBuilder gb = new GridBuilder(b);
+        GridRowBuilder gb = new GridRowBuilder(b);
         gb.setPrimaryAction(primaryAction);
-        gb.addCell(new GridBuilder.CellBuilder(gb)
+        gb.addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.weather_1),
                                 SMALL_IMAGE)
                         .addText("MON")
                         .addTitleText("69\u00B0"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.weather_2),
                                 SMALL_IMAGE)
                         .addText("TUE")
                         .addTitleText("71\u00B0"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.weather_3),
                                 SMALL_IMAGE)
                         .addText("WED")
                         .addTitleText("76\u00B0"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.weather_4),
                                 SMALL_IMAGE)
                         .addText("THU")
                         .addTitleText("72\u00B0"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.weather_1),
                                 SMALL_IMAGE)
                         .addText("FRI")
                         .addTitleText("68\u00B0"));
-        return b.addGrid(gb).build();
+        return b.addGridRow(gb).build();
     }
 
     private Slice createGallery(Uri sliceUri) {
         ListBuilder b = new ListBuilder(getContext(), sliceUri);
-        GridBuilder gb = new GridBuilder(b);
+        GridRowBuilder gb = new GridRowBuilder(b);
         PendingIntent pi = getBroadcastIntent(ACTION_TOAST, "see more of your gallery");
         gb.addSeeMoreAction(pi);
-        gb.addCell(new GridBuilder.CellBuilder(gb)
+        gb.addCell(new GridRowBuilder.CellBuilder(gb)
                 .addImage(Icon.createWithResource(getContext(), R.drawable.slices_1),
                         LARGE_IMAGE))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.slices_2),
                                 LARGE_IMAGE))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.slices_3),
                                 LARGE_IMAGE))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.slices_4),
                                 LARGE_IMAGE))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.slices_2),
                                 LARGE_IMAGE))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.slices_3),
                                 LARGE_IMAGE))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.slices_4),
                                 LARGE_IMAGE));
-        return b.addGrid(gb).build();
+        return b.addGridRow(gb).build();
     }
 
     private Slice createSubSlice(Uri sliceUri, boolean customSeeMore) {
         ListBuilder b = new ListBuilder(getContext(), sliceUri);
-        GridBuilder gb = new GridBuilder(b);
-        GridBuilder.CellBuilder cb = new GridBuilder.CellBuilder(gb);
+        GridRowBuilder gb = new GridRowBuilder(b);
+        GridRowBuilder.CellBuilder cb = new GridRowBuilder.CellBuilder(gb);
         PendingIntent pi = getBroadcastIntent(ACTION_TOAST, "See cats you follow");
         if (customSeeMore) {
             cb.addImage(Icon.createWithResource(getContext(), R.drawable.ic_right_caret),
@@ -190,31 +190,31 @@
         } else {
             gb.addSeeMoreAction(pi);
         }
-        gb.addCell(new GridBuilder.CellBuilder(gb)
+        gb.addCell(new GridRowBuilder.CellBuilder(gb)
                     .addImage(Icon.createWithResource(getContext(), R.drawable.cat_1),
                             SMALL_IMAGE)
                     .addTitleText("Oreo"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_2),
                                 SMALL_IMAGE)
                         .addTitleText("Silver"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_3),
                                 SMALL_IMAGE)
                         .addTitleText("Drake"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_5),
                                 SMALL_IMAGE)
                         .addTitleText("Olive"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_4),
                                 SMALL_IMAGE)
                         .addTitleText("Lady Marmalade"))
-                .addCell(new GridBuilder.CellBuilder(gb)
+                .addCell(new GridRowBuilder.CellBuilder(gb)
                         .addImage(Icon.createWithResource(getContext(), R.drawable.cat_6),
                                 SMALL_IMAGE)
                         .addTitleText("Grapefruit"));
-        return b.addGrid(gb).build();
+        return b.addGridRow(gb).build();
     }
 
     private Slice createContact(Uri sliceUri) {
@@ -223,29 +223,29 @@
         SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST,
                 "See contact info"), Icon.createWithResource(getContext(),
                 R.drawable.mady), SMALL_IMAGE, "Mady");
-        GridBuilder gb = new GridBuilder(b);
+        GridRowBuilder gb = new GridRowBuilder(b);
         return b.setColor(0xff3949ab)
                 .addRow(rb
                         .setTitle("Mady Pitza")
                         .setSubtitle("Frequently contacted contact")
                         .addEndItem(primaryAction))
-                .addGrid(gb
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                .addGridRow(gb
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                             .addImage(Icon.createWithResource(getContext(), R.drawable.ic_call),
                                     ICON_IMAGE)
                             .addText("Call")
                             .setContentIntent(getBroadcastIntent(ACTION_TOAST, "call")))
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                             .addImage(Icon.createWithResource(getContext(), R.drawable.ic_text),
                                     ICON_IMAGE)
                             .addText("Text")
                             .setContentIntent(getBroadcastIntent(ACTION_TOAST, "text")))
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                             .addImage(Icon.createWithResource(getContext(), R.drawable.ic_video),
                                     ICON_IMAGE)
                             .setContentIntent(getBroadcastIntent(ACTION_TOAST, "video"))
                             .addText("Video"))
-                        .addCell(new GridBuilder.CellBuilder(gb)
+                        .addCell(new GridRowBuilder.CellBuilder(gb)
                             .addImage(Icon.createWithResource(getContext(), R.drawable.ic_email),
                                     ICON_IMAGE)
                             .addText("Email")
@@ -423,16 +423,16 @@
 
     private Slice createReservationSlice(Uri sliceUri) {
         ListBuilder lb = new ListBuilder(getContext(), sliceUri);
-        GridBuilder gb1 = new GridBuilder(lb);
-        gb1.addCell(new GridBuilder.CellBuilder(gb1)
+        GridRowBuilder gb1 = new GridRowBuilder(lb);
+        gb1.addCell(new GridRowBuilder.CellBuilder(gb1)
                 .addImage(Icon.createWithResource(getContext(), R.drawable.reservation),
                         LARGE_IMAGE)
                 .setContentDescription("Image of your reservation in Seattle"));
-        GridBuilder gb2 = new GridBuilder(lb);
-        gb2.addCell(new GridBuilder.CellBuilder(gb2)
+        GridRowBuilder gb2 = new GridRowBuilder(lb);
+        gb2.addCell(new GridRowBuilder.CellBuilder(gb2)
                     .addTitleText("Check In")
                     .addText("12:00 PM, Feb 1"))
-                .addCell(new GridBuilder.CellBuilder(gb2)
+                .addCell(new GridRowBuilder.CellBuilder(gb2)
                     .addTitleText("Check Out")
                     .addText("11:00 AM, Feb 19"));
         return lb.setColor(0xffFF5252)
@@ -446,8 +446,8 @@
                 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "contact host"),
                         Icon.createWithResource(getContext(), R.drawable.ic_text),
                         "Contact host"))
-                .addGrid(gb1)
-                .addGrid(gb2)
+                .addGridRow(gb1)
+                .addGridRow(gb2)
                 .build();
     }
 
diff --git a/slices/view/src/main/java/androidx/slice/SliceManager.java b/slices/view/src/main/java/androidx/slice/SliceManager.java
index 2f470ab..a97acd0 100644
--- a/slices/view/src/main/java/androidx/slice/SliceManager.java
+++ b/slices/view/src/main/java/androidx/slice/SliceManager.java
@@ -19,12 +19,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.core.os.BuildCompat;
 
-import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -125,7 +126,7 @@
      * @see SliceSpec
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public abstract @NonNull List<SliceSpec> getPinnedSpecs(@NonNull Uri uri);
+    public abstract @NonNull Set<SliceSpec> getPinnedSpecs(@NonNull Uri uri);
 
     /**
      * Turns a slice Uri into slice content.
diff --git a/slices/view/src/main/java/androidx/slice/SliceManagerBase.java b/slices/view/src/main/java/androidx/slice/SliceManagerBase.java
index ee039c9..e68223b 100644
--- a/slices/view/src/main/java/androidx/slice/SliceManagerBase.java
+++ b/slices/view/src/main/java/androidx/slice/SliceManagerBase.java
@@ -24,11 +24,12 @@
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Looper;
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
 import android.util.ArrayMap;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
 import java.util.concurrent.Executor;
 
 /**
@@ -58,13 +59,11 @@
     @Override
     public void registerSliceCallback(@NonNull Uri uri, @NonNull Executor executor,
             @NonNull SliceCallback callback) {
-        pinSlice(uri);
         getListener(uri, callback, new SliceListenerImpl(uri, executor, callback)).startListening();
     }
 
     @Override
     public void unregisterSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback) {
-        unpinSlice(uri);
         SliceListenerImpl impl = mListenerLookup.remove(new Pair<>(uri, callback));
         if (impl != null) impl.stopListening();
     }
@@ -84,6 +83,7 @@
         private Uri mUri;
         private final Executor mExecutor;
         private final SliceCallback mCallback;
+        private boolean mPinned;
 
         SliceListenerImpl(Uri uri, Executor executor, SliceCallback callback) {
             mUri = uri;
@@ -93,15 +93,32 @@
 
         void startListening() {
             mContext.getContentResolver().registerContentObserver(mUri, true, mObserver);
+            tryPin();
+        }
+
+        private void tryPin() {
+            if (!mPinned) {
+                try {
+                    pinSlice(mUri);
+                    mPinned = true;
+                } catch (SecurityException e) {
+                    // No permission currently.
+                }
+            }
         }
 
         void stopListening() {
             mContext.getContentResolver().unregisterContentObserver(mObserver);
+            if (mPinned) {
+                unpinSlice(mUri);
+                mPinned = false;
+            }
         }
 
         private final Runnable mUpdateSlice = new Runnable() {
             @Override
             public void run() {
+                tryPin();
                 final Slice s = Slice.bindSlice(mContext, mUri, SUPPORTED_SPECS);
                 mExecutor.execute(new Runnable() {
                     @Override
diff --git a/slices/view/src/main/java/androidx/slice/SliceManagerCompat.java b/slices/view/src/main/java/androidx/slice/SliceManagerCompat.java
index 0b9e67e..8abacd1 100644
--- a/slices/view/src/main/java/androidx/slice/SliceManagerCompat.java
+++ b/slices/view/src/main/java/androidx/slice/SliceManagerCompat.java
@@ -21,15 +21,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-
-import java.util.List;
-
 import androidx.slice.compat.SliceProviderCompat;
 import androidx.slice.widget.SliceLiveData;
 
+import java.util.Set;
+
 
 /**
  * @hide
@@ -52,7 +52,7 @@
     }
 
     @Override
-    public @NonNull List<SliceSpec> getPinnedSpecs(@NonNull Uri uri) {
+    public @NonNull Set<SliceSpec> getPinnedSpecs(@NonNull Uri uri) {
         return SliceProviderCompat.getPinnedSpecs(mContext, uri);
     }
 
diff --git a/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java b/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java
index 6b857f4..040baf6 100644
--- a/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java
+++ b/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java
@@ -24,12 +24,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @hide
@@ -54,7 +57,7 @@
     SliceManagerWrapper(Context context, android.app.slice.SliceManager manager) {
         super(context);
         mManager = manager;
-        mSpecs = unwrap(SUPPORTED_SPECS);
+        mSpecs = new ArrayList<>(unwrap(SUPPORTED_SPECS));
     }
 
     @Override
@@ -68,7 +71,7 @@
     }
 
     @Override
-    public @NonNull List<androidx.slice.SliceSpec> getPinnedSpecs(@NonNull Uri uri) {
+    public @NonNull Set<androidx.slice.SliceSpec> getPinnedSpecs(@NonNull Uri uri) {
         return SliceConvert.wrap(mManager.getPinnedSpecs(uri));
     }
 
diff --git a/slices/view/src/main/java/androidx/slice/widget/ActionContent.java b/slices/view/src/main/java/androidx/slice/widget/ActionContent.java
index aedd068..877492d 100644
--- a/slices/view/src/main/java/androidx/slice/widget/ActionContent.java
+++ b/slices/view/src/main/java/androidx/slice/widget/ActionContent.java
@@ -77,6 +77,8 @@
                     SUBTYPE_PRIORITY);
             mPriority = priority != null ? priority.getInt() : -1;
             return true;
+        } else if (FORMAT_ACTION.equals(slice.getFormat())) {
+            mActionItem = slice;
         }
         return false;
     }
diff --git a/slices/view/src/main/java/androidx/slice/widget/RowContent.java b/slices/view/src/main/java/androidx/slice/widget/RowContent.java
index b6ca48a..e08ceb0 100644
--- a/slices/view/src/main/java/androidx/slice/widget/RowContent.java
+++ b/slices/view/src/main/java/androidx/slice/widget/RowContent.java
@@ -37,9 +37,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
@@ -47,6 +44,9 @@
 import androidx.slice.core.SliceQuery;
 import androidx.slice.view.R;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Extracts information required to present content in a row format from a slice.
  * @hide
@@ -91,6 +91,11 @@
         mPrimaryAction = SliceQuery.find(rowSlice, FORMAT_SLICE, hints,
                 new String[] { HINT_ACTIONS, HINT_KEY_WORDS } /* nonHints */);
 
+        if (mPrimaryAction == null && FORMAT_ACTION.equals(rowSlice.getFormat())
+                && rowSlice.getSlice().getItems().size() == 1) {
+            mPrimaryAction = rowSlice;
+        }
+
         mContentDescr = SliceQuery.findSubtype(rowSlice, FORMAT_TEXT, SUBTYPE_CONTENT_DESCRIPTION);
 
         // Filter anything not viable for displaying in a row
diff --git a/slices/view/src/main/java/androidx/slice/widget/RowView.java b/slices/view/src/main/java/androidx/slice/widget/RowView.java
index 13e6128..18b0d54 100644
--- a/slices/view/src/main/java/androidx/slice/widget/RowView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/RowView.java
@@ -51,9 +51,6 @@
 import android.widget.TextView;
 import android.widget.ToggleButton;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import androidx.annotation.ColorInt;
 import androidx.annotation.RestrictTo;
 import androidx.slice.Slice;
@@ -61,6 +58,9 @@
 import androidx.slice.core.SliceQuery;
 import androidx.slice.view.R;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Row item is in small template format and can be used to construct list items for use
  * with {@link LargeTemplateView}.
@@ -305,11 +305,18 @@
     }
 
     private void addRange(final SliceItem range) {
-        final ProgressBar progressBar;
-        if (FORMAT_ACTION.equals(range.getFormat())) {
-            // An input range is displayed as a seek bar
-            progressBar = mSeekBar;
-            mSeekBar.setVisibility(View.VISIBLE);
+        final boolean isSeekBar = FORMAT_ACTION.equals(range.getFormat());
+        final ProgressBar progressBar = isSeekBar ? mSeekBar : mProgressBar;
+        SliceItem max = SliceQuery.findSubtype(range, FORMAT_INT, SUBTYPE_MAX);
+        if (max != null) {
+            progressBar.setMax(max.getInt());
+        }
+        SliceItem progress = SliceQuery.findSubtype(range, FORMAT_INT, SUBTYPE_VALUE);
+        if (progress != null) {
+            progressBar.setProgress(progress.getInt());
+        }
+        progressBar.setVisibility(View.VISIBLE);
+        if (isSeekBar) {
             SliceItem thumb = SliceQuery.find(range, FORMAT_IMAGE);
             if (thumb != null) {
                 mSeekBar.setThumb(thumb.getIcon().loadDrawable(getContext()));
@@ -331,18 +338,6 @@
                 @Override
                 public void onStopTrackingTouch(SeekBar seekBar) { }
             });
-        } else {
-            // A range is displayed as a progress bar.
-            progressBar = mProgressBar;
-            mProgressBar.setVisibility(View.VISIBLE);
-        }
-        SliceItem max = SliceQuery.findSubtype(range, FORMAT_INT, SUBTYPE_MAX);
-        if (max != null) {
-            progressBar.setMax(max.getInt());
-        }
-        SliceItem progress = SliceQuery.findSubtype(range, FORMAT_INT, SUBTYPE_VALUE);
-        if (progress != null) {
-            progressBar.setProgress(progress.getInt());
         }
     }
 
diff --git a/slices/view/src/main/java/androidx/slice/widget/SliceLiveData.java b/slices/view/src/main/java/androidx/slice/widget/SliceLiveData.java
index da6fb2c..aa73ad5 100644
--- a/slices/view/src/main/java/androidx/slice/widget/SliceLiveData.java
+++ b/slices/view/src/main/java/androidx/slice/widget/SliceLiveData.java
@@ -17,22 +17,23 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
-import android.arch.lifecycle.LiveData;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.AsyncTask;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
-
-import java.util.Arrays;
-import java.util.List;
-
+import androidx.collection.ArraySet;
+import androidx.lifecycle.LiveData;
 import androidx.slice.Slice;
 import androidx.slice.SliceManager;
 import androidx.slice.SliceSpec;
 import androidx.slice.SliceSpecs;
 
+import java.util.Arrays;
+import java.util.Set;
+
 /**
  * Class with factory methods for creating LiveData that observes slices.
  *
@@ -45,8 +46,20 @@
      * @hide
      */
     @RestrictTo(LIBRARY)
-    public static final List<SliceSpec> SUPPORTED_SPECS = Arrays.asList(SliceSpecs.BASIC,
-            SliceSpecs.LIST);
+    public static final SliceSpec OLD_BASIC = new SliceSpec("androidx.app.slice.BASIC", 1);
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final SliceSpec OLD_LIST = new SliceSpec("androidx.app.slice.LIST", 1);
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    public static final Set<SliceSpec> SUPPORTED_SPECS = new ArraySet<>(
+            Arrays.asList(SliceSpecs.BASIC, SliceSpecs.LIST, OLD_BASIC, OLD_LIST));
 
     /**
      * Produces an {@link LiveData} that tracks a Slice for a given Uri. To use
diff --git a/slices/view/src/main/java/androidx/slice/widget/SliceView.java b/slices/view/src/main/java/androidx/slice/widget/SliceView.java
index 6ef3103..9fb977e 100644
--- a/slices/view/src/main/java/androidx/slice/widget/SliceView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/SliceView.java
@@ -20,27 +20,27 @@
 import static android.app.slice.Slice.SUBTYPE_COLOR;
 import static android.app.slice.SliceItem.FORMAT_INT;
 
-import android.arch.lifecycle.Observer;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
-import java.util.List;
-
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.Observer;
 import androidx.slice.Slice;
 import androidx.slice.SliceItem;
 import androidx.slice.SliceUtils;
 import androidx.slice.core.SliceQuery;
 import androidx.slice.view.R;
 
+import java.util.List;
+
 /**
  * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
  * able to present slice content in a templated format outside of the associated app. The way this
diff --git a/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java b/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
index 17128ea..ab69d56 100644
--- a/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
+++ b/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
@@ -41,7 +41,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
-import androidx.annotation.RequiresApi;
 import androidx.core.content.ContextCompat;
 import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.ViewCompat;
@@ -197,18 +196,6 @@
 
     final ArrayList<DisableLayerRunnable> mPostedRunnables = new ArrayList<>();
 
-    static final SlidingPanelLayoutImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 17) {
-            IMPL = new SlidingPanelLayoutImplJBMR1();
-        } else if (Build.VERSION.SDK_INT >= 16) {
-            IMPL = new SlidingPanelLayoutImplJB();
-        } else {
-            IMPL = new SlidingPanelLayoutImplBase();
-        }
-    }
-
     /**
      * Listener for monitoring events about sliding panes.
      */
@@ -1020,8 +1007,56 @@
         return result;
     }
 
+    private Method mGetDisplayList;
+    private Field mRecreateDisplayList;
+    private boolean mDisplayListReflectionLoaded;
+
     void invalidateChildRegion(View v) {
-        IMPL.invalidateChildRegion(this, v);
+        if (Build.VERSION.SDK_INT >= 17) {
+            ViewCompat.setLayerPaint(v, ((LayoutParams) v.getLayoutParams()).dimPaint);
+            return;
+        }
+
+        if (Build.VERSION.SDK_INT >= 16) {
+            // Private API hacks! Nasty! Bad!
+            //
+            // In Jellybean, some optimizations in the hardware UI renderer
+            // prevent a changed Paint on a View using a hardware layer from having
+            // the intended effect. This twiddles some internal bits on the view to force
+            // it to recreate the display list.
+            if (!mDisplayListReflectionLoaded) {
+                try {
+                    mGetDisplayList = View.class.getDeclaredMethod("getDisplayList",
+                            (Class[]) null);
+                } catch (NoSuchMethodException e) {
+                    Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.",
+                            e);
+                }
+                try {
+                    mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
+                    mRecreateDisplayList.setAccessible(true);
+                } catch (NoSuchFieldException e) {
+                    Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.",
+                            e);
+                }
+                mDisplayListReflectionLoaded = true;
+            }
+            if (mGetDisplayList == null || mRecreateDisplayList == null) {
+                // Slow path. REALLY slow path. Let's hope we don't get here.
+                v.invalidate();
+                return;
+            }
+
+            try {
+                mRecreateDisplayList.setBoolean(v, true);
+                mGetDisplayList.invoke(v, (Object[]) null);
+            } catch (Exception e) {
+                Log.e(TAG, "Error refreshing display list state", e);
+            }
+        }
+
+        ViewCompat.postInvalidateOnAnimation(this, v.getLeft(), v.getTop(), v.getRight(),
+                v.getBottom());
     }
 
     /**
@@ -1475,71 +1510,6 @@
         };
     }
 
-    interface SlidingPanelLayoutImpl {
-        void invalidateChildRegion(SlidingPaneLayout parent, View child);
-    }
-
-    static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl {
-        @Override
-        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
-            ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(),
-                    child.getRight(), child.getBottom());
-        }
-    }
-
-    @RequiresApi(16)
-    static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase {
-        /*
-         * Private API hacks! Nasty! Bad!
-         *
-         * In Jellybean, some optimizations in the hardware UI renderer
-         * prevent a changed Paint on a View using a hardware layer from having
-         * the intended effect. This twiddles some internal bits on the view to force
-         * it to recreate the display list.
-         */
-        private Method mGetDisplayList;
-        private Field mRecreateDisplayList;
-
-        SlidingPanelLayoutImplJB() {
-            try {
-                mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null);
-            } catch (NoSuchMethodException e) {
-                Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e);
-            }
-            try {
-                mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
-                mRecreateDisplayList.setAccessible(true);
-            } catch (NoSuchFieldException e) {
-                Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e);
-            }
-        }
-
-        @Override
-        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
-            if (mGetDisplayList != null && mRecreateDisplayList != null) {
-                try {
-                    mRecreateDisplayList.setBoolean(child, true);
-                    mGetDisplayList.invoke(child, (Object[]) null);
-                } catch (Exception e) {
-                    Log.e(TAG, "Error refreshing display list state", e);
-                }
-            } else {
-                // Slow path. REALLY slow path. Let's hope we don't get here.
-                child.invalidate();
-                return;
-            }
-            super.invalidateChildRegion(parent, child);
-        }
-    }
-
-    @RequiresApi(17)
-    static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase {
-        @Override
-        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
-            ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint);
-        }
-    }
-
     class AccessibilityDelegate extends AccessibilityDelegateCompat {
         private final Rect mTmpRect = new Rect();
 
diff --git a/transition/src/main/java/androidx/transition/AnimatorUtils.java b/transition/src/main/java/androidx/transition/AnimatorUtils.java
index 511528b..452bc66 100644
--- a/transition/src/main/java/androidx/transition/AnimatorUtils.java
+++ b/transition/src/main/java/androidx/transition/AnimatorUtils.java
@@ -22,29 +22,59 @@
 
 import androidx.annotation.NonNull;
 
+import java.util.ArrayList;
+
 class AnimatorUtils {
 
-    private static final AnimatorUtilsImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 19) {
-            IMPL = new AnimatorUtilsApi19();
-        } else {
-            IMPL = new AnimatorUtilsApi14();
-        }
-    }
-
     static void addPauseListener(@NonNull Animator animator,
             @NonNull AnimatorListenerAdapter listener) {
-        IMPL.addPauseListener(animator, listener);
+        if (Build.VERSION.SDK_INT >= 19) {
+            animator.addPauseListener(listener);
+        }
     }
 
     static void pause(@NonNull Animator animator) {
-        IMPL.pause(animator);
+        if (Build.VERSION.SDK_INT >= 19) {
+            animator.pause();
+        } else {
+            final ArrayList<Animator.AnimatorListener> listeners = animator.getListeners();
+            if (listeners != null) {
+                for (int i = 0, size = listeners.size(); i < size; i++) {
+                    final Animator.AnimatorListener listener = listeners.get(i);
+                    if (listener instanceof AnimatorPauseListenerCompat) {
+                        ((AnimatorPauseListenerCompat) listener).onAnimationPause(animator);
+                    }
+                }
+            }
+        }
     }
 
     static void resume(@NonNull Animator animator) {
-        IMPL.resume(animator);
+        if (Build.VERSION.SDK_INT >= 19) {
+            animator.resume();
+        } else {
+            final ArrayList<Animator.AnimatorListener> listeners = animator.getListeners();
+            if (listeners != null) {
+                for (int i = 0, size = listeners.size(); i < size; i++) {
+                    final Animator.AnimatorListener listener = listeners.get(i);
+                    if (listener instanceof AnimatorPauseListenerCompat) {
+                        ((AnimatorPauseListenerCompat) listener).onAnimationResume(animator);
+                    }
+                }
+            }
+        }
     }
 
+    /**
+     * Listeners can implement this interface in addition to the platform AnimatorPauseListener to
+     * make them compatible with API level 18 and below. Animators will not be paused or resumed,
+     * but the callbacks here are invoked.
+     */
+    interface AnimatorPauseListenerCompat {
+
+        void onAnimationPause(Animator animation);
+
+        void onAnimationResume(Animator animation);
+
+    }
 }
diff --git a/transition/src/main/java/androidx/transition/AnimatorUtilsApi14.java b/transition/src/main/java/androidx/transition/AnimatorUtilsApi14.java
deleted file mode 100644
index df2c680..0000000
--- a/transition/src/main/java/androidx/transition/AnimatorUtilsApi14.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.util.ArrayList;
-
-@RequiresApi(14)
-class AnimatorUtilsApi14 implements AnimatorUtilsImpl {
-
-    @Override
-    public void addPauseListener(@NonNull Animator animator,
-            @NonNull AnimatorListenerAdapter listener) {
-        // Do nothing
-    }
-
-    @Override
-    public void pause(@NonNull Animator animator) {
-        final ArrayList<Animator.AnimatorListener> listeners = animator.getListeners();
-        if (listeners != null) {
-            for (int i = 0, size = listeners.size(); i < size; i++) {
-                final Animator.AnimatorListener listener = listeners.get(i);
-                if (listener instanceof AnimatorPauseListenerCompat) {
-                    ((AnimatorPauseListenerCompat) listener).onAnimationPause(animator);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void resume(@NonNull Animator animator) {
-        final ArrayList<Animator.AnimatorListener> listeners = animator.getListeners();
-        if (listeners != null) {
-            for (int i = 0, size = listeners.size(); i < size; i++) {
-                final Animator.AnimatorListener listener = listeners.get(i);
-                if (listener instanceof AnimatorPauseListenerCompat) {
-                    ((AnimatorPauseListenerCompat) listener).onAnimationResume(animator);
-                }
-            }
-        }
-    }
-
-    /**
-     * Listeners can implement this interface in addition to the platform AnimatorPauseListener to
-     * make them compatible with API level 18 and below. Animators will not be paused or resumed,
-     * but the callbacks here are invoked.
-     */
-    interface AnimatorPauseListenerCompat {
-
-        void onAnimationPause(Animator animation);
-
-        void onAnimationResume(Animator animation);
-
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/AnimatorUtilsApi19.java b/transition/src/main/java/androidx/transition/AnimatorUtilsApi19.java
deleted file mode 100644
index 8bab629..0000000
--- a/transition/src/main/java/androidx/transition/AnimatorUtilsApi19.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.transition;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(19)
-class AnimatorUtilsApi19 implements AnimatorUtilsImpl {
-
-    @Override
-    public void addPauseListener(@NonNull Animator animator,
-            @NonNull AnimatorListenerAdapter listener) {
-        animator.addPauseListener(listener);
-    }
-
-    @Override
-    public void pause(@NonNull Animator animator) {
-        animator.pause();
-    }
-
-    @Override
-    public void resume(@NonNull Animator animator) {
-        animator.resume();
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/AnimatorUtilsImpl.java b/transition/src/main/java/androidx/transition/AnimatorUtilsImpl.java
deleted file mode 100644
index 5f61cea..0000000
--- a/transition/src/main/java/androidx/transition/AnimatorUtilsImpl.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-
-import androidx.annotation.NonNull;
-
-interface AnimatorUtilsImpl {
-
-    void addPauseListener(@NonNull Animator animator, @NonNull AnimatorListenerAdapter listener);
-
-    void pause(@NonNull Animator animator);
-
-    void resume(@NonNull Animator animator);
-
-}
diff --git a/transition/src/main/java/androidx/transition/GhostViewApi14.java b/transition/src/main/java/androidx/transition/GhostViewApi14.java
index f989e88..fa577d9 100644
--- a/transition/src/main/java/androidx/transition/GhostViewApi14.java
+++ b/transition/src/main/java/androidx/transition/GhostViewApi14.java
@@ -26,7 +26,6 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 import androidx.core.view.ViewCompat;
 
 /**
@@ -40,58 +39,51 @@
  * that, this view is sized as large as the parent FrameLayout (except padding) while the platform
  * version becomes as large as the target view.
  */
-@RequiresApi(14)
 @SuppressLint("ViewConstructor")
 class GhostViewApi14 extends View implements GhostViewImpl {
 
-    static class Creator implements GhostViewImpl.Creator {
-
-        @Override
-        public GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
-            GhostViewApi14 ghostView = getGhostView(view);
-            if (ghostView == null) {
-                FrameLayout frameLayout = findFrameLayout(viewGroup);
-                if (frameLayout == null) {
-                    return null;
-                }
-                ghostView = new GhostViewApi14(view);
-                frameLayout.addView(ghostView);
+    static GhostViewImpl addGhost(View view, ViewGroup viewGroup) {
+        GhostViewApi14 ghostView = getGhostView(view);
+        if (ghostView == null) {
+            FrameLayout frameLayout = findFrameLayout(viewGroup);
+            if (frameLayout == null) {
+                return null;
             }
-            ghostView.mReferences++;
-            return ghostView;
+            ghostView = new GhostViewApi14(view);
+            frameLayout.addView(ghostView);
         }
+        ghostView.mReferences++;
+        return ghostView;
+    }
 
-        @Override
-        public void removeGhost(View view) {
-            GhostViewApi14 ghostView = getGhostView(view);
-            if (ghostView != null) {
-                ghostView.mReferences--;
-                if (ghostView.mReferences <= 0) {
-                    ViewParent parent = ghostView.getParent();
-                    if (parent instanceof ViewGroup) {
-                        ViewGroup group = (ViewGroup) parent;
-                        group.endViewTransition(ghostView);
-                        group.removeView(ghostView);
-                    }
+    static void removeGhost(View view) {
+        GhostViewApi14 ghostView = getGhostView(view);
+        if (ghostView != null) {
+            ghostView.mReferences--;
+            if (ghostView.mReferences <= 0) {
+                ViewParent parent = ghostView.getParent();
+                if (parent instanceof ViewGroup) {
+                    ViewGroup group = (ViewGroup) parent;
+                    group.endViewTransition(ghostView);
+                    group.removeView(ghostView);
                 }
             }
         }
+    }
 
-        /**
-         * Find the closest FrameLayout in the ascendant hierarchy from the specified {@code
-         * viewGroup}.
-         */
-        private static FrameLayout findFrameLayout(ViewGroup viewGroup) {
-            while (!(viewGroup instanceof FrameLayout)) {
-                ViewParent parent = viewGroup.getParent();
-                if (!(parent instanceof ViewGroup)) {
-                    return null;
-                }
-                viewGroup = (ViewGroup) parent;
+    /**
+     * Find the closest FrameLayout in the ascendant hierarchy from the specified {@code
+     * viewGroup}.
+     */
+    private static FrameLayout findFrameLayout(ViewGroup viewGroup) {
+        while (!(viewGroup instanceof FrameLayout)) {
+            ViewParent parent = viewGroup.getParent();
+            if (!(parent instanceof ViewGroup)) {
+                return null;
             }
-            return (FrameLayout) viewGroup;
+            viewGroup = (ViewGroup) parent;
         }
-
+        return (FrameLayout) viewGroup;
     }
 
     /** The target view */
diff --git a/transition/src/main/java/androidx/transition/GhostViewApi21.java b/transition/src/main/java/androidx/transition/GhostViewApi21.java
index 70897e9..4cf5ae8 100644
--- a/transition/src/main/java/androidx/transition/GhostViewApi21.java
+++ b/transition/src/main/java/androidx/transition/GhostViewApi21.java
@@ -39,38 +39,32 @@
     private static Method sRemoveGhostMethod;
     private static boolean sRemoveGhostMethodFetched;
 
-    static class Creator implements GhostViewImpl.Creator {
-
-        @Override
-        public GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
-            fetchAddGhostMethod();
-            if (sAddGhostMethod != null) {
-                try {
-                    return new GhostViewApi21(
-                            (View) sAddGhostMethod.invoke(null, view, viewGroup, matrix));
-                } catch (IllegalAccessException e) {
-                    // Do nothing
-                } catch (InvocationTargetException e) {
-                    throw new RuntimeException(e.getCause());
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public void removeGhost(View view) {
-            fetchRemoveGhostMethod();
-            if (sRemoveGhostMethod != null) {
-                try {
-                    sRemoveGhostMethod.invoke(null, view);
-                } catch (IllegalAccessException e) {
-                    // Do nothing
-                } catch (InvocationTargetException e) {
-                    throw new RuntimeException(e.getCause());
-                }
+    static GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
+        fetchAddGhostMethod();
+        if (sAddGhostMethod != null) {
+            try {
+                return new GhostViewApi21(
+                        (View) sAddGhostMethod.invoke(null, view, viewGroup, matrix));
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
             }
         }
+        return null;
+    }
 
+    static void removeGhost(View view) {
+        fetchRemoveGhostMethod();
+        if (sRemoveGhostMethod != null) {
+            try {
+                sRemoveGhostMethod.invoke(null, view);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
     }
 
     /** A handle to the platform android.view.GhostView. */
diff --git a/transition/src/main/java/androidx/transition/GhostViewImpl.java b/transition/src/main/java/androidx/transition/GhostViewImpl.java
index 42a4593..5a4d6cf 100644
--- a/transition/src/main/java/androidx/transition/GhostViewImpl.java
+++ b/transition/src/main/java/androidx/transition/GhostViewImpl.java
@@ -16,23 +16,11 @@
 
 package androidx.transition;
 
-import android.graphics.Matrix;
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
 interface GhostViewImpl {
 
-    interface Creator {
-
-        GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix);
-
-        void removeGhost(View view);
-
-    }
-
     void setVisibility(int visibility);
 
     /**
diff --git a/transition/src/main/java/androidx/transition/GhostViewUtils.java b/transition/src/main/java/androidx/transition/GhostViewUtils.java
index 8e8a752..447f728 100644
--- a/transition/src/main/java/androidx/transition/GhostViewUtils.java
+++ b/transition/src/main/java/androidx/transition/GhostViewUtils.java
@@ -23,22 +23,19 @@
 
 class GhostViewUtils {
 
-    private static final GhostViewImpl.Creator CREATOR;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            CREATOR = new GhostViewApi21.Creator();
-        } else {
-            CREATOR = new GhostViewApi14.Creator();
-        }
-    }
-
     static GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
-        return CREATOR.addGhost(view, viewGroup, matrix);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return GhostViewApi21.addGhost(view, viewGroup, matrix);
+        }
+        return GhostViewApi14.addGhost(view, viewGroup);
     }
 
     static void removeGhost(View view) {
-        CREATOR.removeGhost(view);
+        if (Build.VERSION.SDK_INT >= 21) {
+            GhostViewApi21.removeGhost(view);
+        } else {
+            GhostViewApi14.removeGhost(view);
+        }
     }
 
 }
diff --git a/transition/src/main/java/androidx/transition/ImageViewUtils.java b/transition/src/main/java/androidx/transition/ImageViewUtils.java
index 8bdec01..a7c947c 100644
--- a/transition/src/main/java/androidx/transition/ImageViewUtils.java
+++ b/transition/src/main/java/androidx/transition/ImageViewUtils.java
@@ -17,43 +17,92 @@
 package androidx.transition;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.graphics.Matrix;
 import android.os.Build;
+import android.util.Log;
 import android.widget.ImageView;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 class ImageViewUtils {
+    private static final String TAG = "ImageViewUtils";
 
-    private static final ImageViewUtilsImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new ImageViewUtilsApi21();
-        } else {
-            IMPL = new ImageViewUtilsApi14();
-        }
-    }
+    private static Method sAnimateTransformMethod;
+    private static boolean sAnimateTransformMethodFetched;
 
     /**
      * Starts animating the transformation of the image view. This has to be called before calling
      * {@link #animateTransform(ImageView, Matrix)}.
      */
     static void startAnimateTransform(ImageView view) {
-        IMPL.startAnimateTransform(view);
+        if (Build.VERSION.SDK_INT < 21) {
+            final ImageView.ScaleType scaleType = view.getScaleType();
+            view.setTag(R.id.save_scale_type, scaleType);
+            if (scaleType == ImageView.ScaleType.MATRIX) {
+                view.setTag(R.id.save_image_matrix, view.getImageMatrix());
+            } else {
+                view.setScaleType(ImageView.ScaleType.MATRIX);
+            }
+            view.setImageMatrix(MatrixUtils.IDENTITY_MATRIX);
+        }
     }
 
     /**
      * Sets the matrix to animate the content of the image view.
      */
     static void animateTransform(ImageView view, Matrix matrix) {
-        IMPL.animateTransform(view, matrix);
+        if (Build.VERSION.SDK_INT < 21) {
+            view.setImageMatrix(matrix);
+        } else {
+            fetchAnimateTransformMethod();
+            if (sAnimateTransformMethod != null) {
+                try {
+                    sAnimateTransformMethod.invoke(view, matrix);
+                } catch (IllegalAccessException e) {
+                    // Do nothing
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException(e.getCause());
+                }
+            }
+        }
+    }
+
+    private static void fetchAnimateTransformMethod() {
+        if (!sAnimateTransformMethodFetched) {
+            try {
+                sAnimateTransformMethod = ImageView.class.getDeclaredMethod("animateTransform",
+                        Matrix.class);
+                sAnimateTransformMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve animateTransform method", e);
+            }
+            sAnimateTransformMethodFetched = true;
+        }
     }
 
     /**
      * Reserves that the caller will stop calling {@link #animateTransform(ImageView, Matrix)} when
      * the specified animator ends.
      */
-    static void reserveEndAnimateTransform(ImageView view, Animator animator) {
-        IMPL.reserveEndAnimateTransform(view, animator);
+    static void reserveEndAnimateTransform(final ImageView view, Animator animator) {
+        if (Build.VERSION.SDK_INT < 21) {
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    final ImageView.ScaleType scaleType = (ImageView.ScaleType)
+                            view.getTag(R.id.save_scale_type);
+                    view.setScaleType(scaleType);
+                    view.setTag(R.id.save_scale_type, null);
+                    if (scaleType == ImageView.ScaleType.MATRIX) {
+                        view.setImageMatrix((Matrix) view.getTag(R.id.save_image_matrix));
+                        view.setTag(R.id.save_image_matrix, null);
+                    }
+                    animation.removeListener(this);
+                }
+            });
+        }
     }
 
 }
diff --git a/transition/src/main/java/androidx/transition/ImageViewUtilsApi14.java b/transition/src/main/java/androidx/transition/ImageViewUtilsApi14.java
deleted file mode 100644
index 4e8f8c1..0000000
--- a/transition/src/main/java/androidx/transition/ImageViewUtilsApi14.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.graphics.Matrix;
-import android.widget.ImageView;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
-class ImageViewUtilsApi14 implements ImageViewUtilsImpl {
-
-    @Override
-    public void startAnimateTransform(ImageView view) {
-        final ImageView.ScaleType scaleType = view.getScaleType();
-        view.setTag(R.id.save_scale_type, scaleType);
-        if (scaleType == ImageView.ScaleType.MATRIX) {
-            view.setTag(R.id.save_image_matrix, view.getImageMatrix());
-        } else {
-            view.setScaleType(ImageView.ScaleType.MATRIX);
-        }
-        view.setImageMatrix(MatrixUtils.IDENTITY_MATRIX);
-    }
-
-    @Override
-    public void animateTransform(ImageView view, Matrix matrix) {
-        view.setImageMatrix(matrix);
-    }
-
-    @Override
-    public void reserveEndAnimateTransform(final ImageView view, Animator animator) {
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                final ImageView.ScaleType scaleType = (ImageView.ScaleType)
-                        view.getTag(R.id.save_scale_type);
-                view.setScaleType(scaleType);
-                view.setTag(R.id.save_scale_type, null);
-                if (scaleType == ImageView.ScaleType.MATRIX) {
-                    view.setImageMatrix((Matrix) view.getTag(R.id.save_image_matrix));
-                    view.setTag(R.id.save_image_matrix, null);
-                }
-                animation.removeListener(this);
-            }
-        });
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/ImageViewUtilsApi21.java b/transition/src/main/java/androidx/transition/ImageViewUtilsApi21.java
deleted file mode 100644
index c5b7f56..0000000
--- a/transition/src/main/java/androidx/transition/ImageViewUtilsApi21.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.Animator;
-import android.graphics.Matrix;
-import android.util.Log;
-import android.widget.ImageView;
-
-import androidx.annotation.RequiresApi;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-@RequiresApi(21)
-class ImageViewUtilsApi21 implements ImageViewUtilsImpl {
-
-    private static final String TAG = "ImageViewUtilsApi21";
-
-    private static Method sAnimateTransformMethod;
-    private static boolean sAnimateTransformMethodFetched;
-
-    @Override
-    public void startAnimateTransform(ImageView view) {
-        // Do nothing
-    }
-
-    @Override
-    public void animateTransform(ImageView view, Matrix matrix) {
-        fetchAnimateTransformMethod();
-        if (sAnimateTransformMethod != null) {
-            try {
-                sAnimateTransformMethod.invoke(view, matrix);
-            } catch (IllegalAccessException e) {
-                // Do nothing
-            } catch (InvocationTargetException e) {
-                throw new RuntimeException(e.getCause());
-            }
-        }
-    }
-
-    @Override
-    public void reserveEndAnimateTransform(ImageView view, Animator animator) {
-        // Do nothing
-    }
-
-    private void fetchAnimateTransformMethod() {
-        if (!sAnimateTransformMethodFetched) {
-            try {
-                sAnimateTransformMethod = ImageView.class.getDeclaredMethod("animateTransform",
-                        Matrix.class);
-                sAnimateTransformMethod.setAccessible(true);
-            } catch (NoSuchMethodException e) {
-                Log.i(TAG, "Failed to retrieve animateTransform method", e);
-            }
-            sAnimateTransformMethodFetched = true;
-        }
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/ImageViewUtilsImpl.java b/transition/src/main/java/androidx/transition/ImageViewUtilsImpl.java
deleted file mode 100644
index 5006de3..0000000
--- a/transition/src/main/java/androidx/transition/ImageViewUtilsImpl.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.Animator;
-import android.graphics.Matrix;
-import android.widget.ImageView;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
-interface ImageViewUtilsImpl {
-
-    void startAnimateTransform(ImageView view);
-
-    void animateTransform(ImageView view, Matrix matrix);
-
-    void reserveEndAnimateTransform(ImageView view, Animator animator);
-
-}
diff --git a/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java b/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
index 281b2ef..a7fb97d 100644
--- a/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
+++ b/transition/src/main/java/androidx/transition/ObjectAnimatorUtils.java
@@ -24,18 +24,11 @@
 
 class ObjectAnimatorUtils {
 
-    private static final ObjectAnimatorUtilsImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new ObjectAnimatorUtilsApi21();
-        } else {
-            IMPL = new ObjectAnimatorUtilsApi14();
-        }
-    }
-
     static <T> ObjectAnimator ofPointF(T target, Property<T, PointF> property, Path path) {
-        return IMPL.ofPointF(target, property, path);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return ObjectAnimator.ofObject(target, property, null, path);
+        }
+        return ObjectAnimator.ofFloat(target, new PathProperty<>(property, path), 0f, 1f);
     }
 
 }
diff --git a/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsApi14.java b/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsApi14.java
deleted file mode 100644
index 92cc5df..0000000
--- a/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsApi14.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.ObjectAnimator;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.util.Property;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
-class ObjectAnimatorUtilsApi14 implements ObjectAnimatorUtilsImpl {
-
-    @Override
-    public <T> ObjectAnimator ofPointF(T target, Property<T, PointF> property, Path path) {
-        return ObjectAnimator.ofFloat(target, new PathProperty<>(property, path), 0f, 1f);
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsApi21.java b/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsApi21.java
deleted file mode 100644
index 09b07bf..0000000
--- a/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsApi21.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.ObjectAnimator;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.util.Property;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(21)
-class ObjectAnimatorUtilsApi21 implements ObjectAnimatorUtilsImpl {
-
-    @Override
-    public <T> ObjectAnimator ofPointF(T target, Property<T, PointF> property, Path path) {
-        return ObjectAnimator.ofObject(target, property, null, path);
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsImpl.java b/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsImpl.java
deleted file mode 100644
index c3af025..0000000
--- a/transition/src/main/java/androidx/transition/ObjectAnimatorUtilsImpl.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.ObjectAnimator;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.util.Property;
-
-interface ObjectAnimatorUtilsImpl {
-
-    <T> ObjectAnimator ofPointF(T target, Property<T, PointF> property, Path path);
-
-}
diff --git a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java b/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
index 819ec0d..2ea657c 100644
--- a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
+++ b/transition/src/main/java/androidx/transition/PropertyValuesHolderUtils.java
@@ -24,16 +24,6 @@
 
 class PropertyValuesHolderUtils {
 
-    private static final PropertyValuesHolderUtilsImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            IMPL = new PropertyValuesHolderUtilsApi21();
-        } else {
-            IMPL = new PropertyValuesHolderUtilsApi14();
-        }
-    }
-
     /**
      * Constructs and returns a PropertyValuesHolder with a given property and
      * a Path along which the values should be animated. This variant supports a
@@ -45,7 +35,10 @@
      * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
      */
     static PropertyValuesHolder ofPointF(Property<?, PointF> property, Path path) {
-        return IMPL.ofPointF(property, path);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return PropertyValuesHolder.ofObject(property, null, path);
+        }
+        return PropertyValuesHolder.ofFloat(new PathProperty<>(property, path), 0f, 1f);
     }
 
 }
diff --git a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsApi14.java b/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsApi14.java
deleted file mode 100644
index 047500d..0000000
--- a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsApi14.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.PropertyValuesHolder;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.util.Property;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
-class PropertyValuesHolderUtilsApi14 implements PropertyValuesHolderUtilsImpl {
-
-    @Override
-    public PropertyValuesHolder ofPointF(Property<?, PointF> property, Path path) {
-        return PropertyValuesHolder.ofFloat(new PathProperty<>(property, path), 0f, 1f);
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsApi21.java b/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsApi21.java
deleted file mode 100644
index c5a1170..0000000
--- a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsApi21.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.PropertyValuesHolder;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.util.Property;
-
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(21)
-class PropertyValuesHolderUtilsApi21 implements PropertyValuesHolderUtilsImpl {
-
-    @Override
-    public PropertyValuesHolder ofPointF(Property<?, PointF> property, Path path) {
-        return PropertyValuesHolder.ofObject(property, null, path);
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsImpl.java b/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsImpl.java
deleted file mode 100644
index b5bd6ea..0000000
--- a/transition/src/main/java/androidx/transition/PropertyValuesHolderUtilsImpl.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.animation.PropertyValuesHolder;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.util.Property;
-
-interface PropertyValuesHolderUtilsImpl {
-
-    PropertyValuesHolder ofPointF(Property<?, PointF> property, Path path);
-
-}
diff --git a/transition/src/main/java/androidx/transition/ViewGroupUtils.java b/transition/src/main/java/androidx/transition/ViewGroupUtils.java
index 0077005..55ab3ff 100644
--- a/transition/src/main/java/androidx/transition/ViewGroupUtils.java
+++ b/transition/src/main/java/androidx/transition/ViewGroupUtils.java
@@ -26,28 +26,25 @@
  */
 class ViewGroupUtils {
 
-    private static final ViewGroupUtilsImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 18) {
-            IMPL = new ViewGroupUtilsApi18();
-        } else {
-            IMPL = new ViewGroupUtilsApi14();
-        }
-    }
-
     /**
      * Backward-compatible {@link ViewGroup#getOverlay()}.
      */
     static ViewGroupOverlayImpl getOverlay(@NonNull ViewGroup group) {
-        return IMPL.getOverlay(group);
+        if (Build.VERSION.SDK_INT >= 18) {
+            return new ViewGroupOverlayApi18(group);
+        }
+        return ViewGroupOverlayApi14.createFrom(group);
     }
 
     /**
      * Provides access to the hidden ViewGroup#suppressLayout method.
      */
     static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
-        IMPL.suppressLayout(group, suppress);
+        if (Build.VERSION.SDK_INT >= 18) {
+            ViewGroupUtilsApi18.suppressLayout(group, suppress);
+        } else {
+            ViewGroupUtilsApi14.suppressLayout(group, suppress);
+        }
     }
 
 }
diff --git a/transition/src/main/java/androidx/transition/ViewGroupUtilsApi14.java b/transition/src/main/java/androidx/transition/ViewGroupUtilsApi14.java
index 3843062..70af497 100644
--- a/transition/src/main/java/androidx/transition/ViewGroupUtilsApi14.java
+++ b/transition/src/main/java/androidx/transition/ViewGroupUtilsApi14.java
@@ -21,14 +21,12 @@
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
-@RequiresApi(14)
-class ViewGroupUtilsApi14 implements ViewGroupUtilsImpl {
+class ViewGroupUtilsApi14 {
 
     private static final String TAG = "ViewGroupUtilsApi14";
 
@@ -42,13 +40,7 @@
     private static Method sCancelMethod;
     private static boolean sCancelMethodFetched;
 
-    @Override
-    public ViewGroupOverlayImpl getOverlay(@NonNull ViewGroup group) {
-        return ViewGroupOverlayApi14.createFrom(group);
-    }
-
-    @Override
-    public void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
+    static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
         // Prepare the dummy LayoutTransition
         if (sEmptyLayoutTransition == null) {
             sEmptyLayoutTransition = new LayoutTransition() {
diff --git a/transition/src/main/java/androidx/transition/ViewGroupUtilsApi18.java b/transition/src/main/java/androidx/transition/ViewGroupUtilsApi18.java
index 94d6dc6..bd0938a 100644
--- a/transition/src/main/java/androidx/transition/ViewGroupUtilsApi18.java
+++ b/transition/src/main/java/androidx/transition/ViewGroupUtilsApi18.java
@@ -26,20 +26,14 @@
 import java.lang.reflect.Method;
 
 @RequiresApi(18)
-class ViewGroupUtilsApi18 extends ViewGroupUtilsApi14 {
+class ViewGroupUtilsApi18 {
 
     private static final String TAG = "ViewUtilsApi18";
 
     private static Method sSuppressLayoutMethod;
     private static boolean sSuppressLayoutMethodFetched;
 
-    @Override
-    public ViewGroupOverlayImpl getOverlay(@NonNull ViewGroup group) {
-        return new ViewGroupOverlayApi18(group);
-    }
-
-    @Override
-    public void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
+    static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
         fetchSuppressLayoutMethod();
         if (sSuppressLayoutMethod != null) {
             try {
@@ -52,7 +46,7 @@
         }
     }
 
-    private void fetchSuppressLayoutMethod() {
+    private static void fetchSuppressLayoutMethod() {
         if (!sSuppressLayoutMethodFetched) {
             try {
                 sSuppressLayoutMethod = ViewGroup.class.getDeclaredMethod("suppressLayout",
diff --git a/transition/src/main/java/androidx/transition/ViewGroupUtilsImpl.java b/transition/src/main/java/androidx/transition/ViewGroupUtilsImpl.java
deleted file mode 100644
index 2de6fde..0000000
--- a/transition/src/main/java/androidx/transition/ViewGroupUtilsImpl.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
-interface ViewGroupUtilsImpl {
-
-    ViewGroupOverlayImpl getOverlay(@NonNull ViewGroup group);
-
-    void suppressLayout(@NonNull ViewGroup group, boolean suppress);
-
-}
diff --git a/transition/src/main/java/androidx/transition/ViewUtils.java b/transition/src/main/java/androidx/transition/ViewUtils.java
index 8200f7c..89eaadd 100644
--- a/transition/src/main/java/androidx/transition/ViewUtils.java
+++ b/transition/src/main/java/androidx/transition/ViewUtils.java
@@ -34,7 +34,7 @@
  */
 class ViewUtils {
 
-    private static final ViewUtilsImpl IMPL;
+    private static final ViewUtilsBase IMPL;
     private static final String TAG = "ViewUtils";
 
     private static Field sViewFlagsField;
@@ -48,10 +48,8 @@
             IMPL = new ViewUtilsApi21();
         } else if (Build.VERSION.SDK_INT >= 19) {
             IMPL = new ViewUtilsApi19();
-        } else if (Build.VERSION.SDK_INT >= 18) {
-            IMPL = new ViewUtilsApi18();
         } else {
-            IMPL = new ViewUtilsApi14();
+            IMPL = new ViewUtilsBase();
         }
     }
 
@@ -92,14 +90,20 @@
      * Backward-compatible {@link View#getOverlay()}.
      */
     static ViewOverlayImpl getOverlay(@NonNull View view) {
-        return IMPL.getOverlay(view);
+        if (Build.VERSION.SDK_INT >= 18) {
+            return new ViewOverlayApi18(view);
+        }
+        return ViewOverlayApi14.createFrom(view);
     }
 
     /**
      * Backward-compatible {@link View#getWindowId()}.
      */
     static WindowIdImpl getWindowId(@NonNull View view) {
-        return IMPL.getWindowId(view);
+        if (Build.VERSION.SDK_INT >= 18) {
+            return new WindowIdApi18(view);
+        }
+        return new WindowIdApi14(view.getWindowToken());
     }
 
     static void setTransitionAlpha(@NonNull View view, float alpha) {
diff --git a/transition/src/main/java/androidx/transition/ViewUtilsApi14.java b/transition/src/main/java/androidx/transition/ViewUtilsApi14.java
deleted file mode 100644
index f52786e..0000000
--- a/transition/src/main/java/androidx/transition/ViewUtilsApi14.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.graphics.Matrix;
-import android.view.View;
-import android.view.ViewParent;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
-class ViewUtilsApi14 implements ViewUtilsImpl {
-
-    private float[] mMatrixValues;
-
-    @Override
-    public ViewOverlayImpl getOverlay(@NonNull View view) {
-        return ViewOverlayApi14.createFrom(view);
-    }
-
-    @Override
-    public WindowIdImpl getWindowId(@NonNull View view) {
-        return new WindowIdApi14(view.getWindowToken());
-    }
-
-    @Override
-    public void setTransitionAlpha(@NonNull View view, float alpha) {
-        Float savedAlpha = (Float) view.getTag(R.id.save_non_transition_alpha);
-        if (savedAlpha != null) {
-            view.setAlpha(savedAlpha * alpha);
-        } else {
-            view.setAlpha(alpha);
-        }
-    }
-
-    @Override
-    public float getTransitionAlpha(@NonNull View view) {
-        Float savedAlpha = (Float) view.getTag(R.id.save_non_transition_alpha);
-        if (savedAlpha != null) {
-            return view.getAlpha() / savedAlpha;
-        } else {
-            return view.getAlpha();
-        }
-    }
-
-    @Override
-    public void saveNonTransitionAlpha(@NonNull View view) {
-        if (view.getTag(R.id.save_non_transition_alpha) == null) {
-            view.setTag(R.id.save_non_transition_alpha, view.getAlpha());
-        }
-    }
-
-    @Override
-    public void clearNonTransitionAlpha(@NonNull View view) {
-        // We don't clear the saved value when the view is hidden; that's the situation we are
-        // saving this value for.
-        if (view.getVisibility() == View.VISIBLE) {
-            view.setTag(R.id.save_non_transition_alpha, null);
-        }
-    }
-
-    @Override
-    public void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix) {
-        final ViewParent parent = view.getParent();
-        if (parent instanceof View) {
-            final View vp = (View) parent;
-            transformMatrixToGlobal(vp, matrix);
-            matrix.preTranslate(-vp.getScrollX(), -vp.getScrollY());
-        }
-        matrix.preTranslate(view.getLeft(), view.getTop());
-        final Matrix vm = view.getMatrix();
-        if (!vm.isIdentity()) {
-            matrix.preConcat(vm);
-        }
-    }
-
-    @Override
-    public void transformMatrixToLocal(@NonNull View view, @NonNull Matrix matrix) {
-        final ViewParent parent = view.getParent();
-        if (parent instanceof View) {
-            final View vp = (View) parent;
-            transformMatrixToLocal(vp, matrix);
-            matrix.postTranslate(vp.getScrollX(), vp.getScrollY());
-        }
-        matrix.postTranslate(view.getLeft(), view.getTop());
-        final Matrix vm = view.getMatrix();
-        if (!vm.isIdentity()) {
-            final Matrix inverted = new Matrix();
-            if (vm.invert(inverted)) {
-                matrix.postConcat(inverted);
-            }
-        }
-    }
-
-    @Override
-    public void setAnimationMatrix(@NonNull View view, Matrix matrix) {
-        if (matrix == null || matrix.isIdentity()) {
-            view.setPivotX(view.getWidth() / 2);
-            view.setPivotY(view.getHeight() / 2);
-            view.setTranslationX(0);
-            view.setTranslationY(0);
-            view.setScaleX(1);
-            view.setScaleY(1);
-            view.setRotation(0);
-        } else {
-            float[] values = mMatrixValues;
-            if (values == null) {
-                mMatrixValues = values = new float[9];
-            }
-            matrix.getValues(values);
-            final float sin = values[Matrix.MSKEW_Y];
-            final float cos = (float) Math.sqrt(1 - sin * sin)
-                    * (values[Matrix.MSCALE_X] < 0 ? -1 : 1);
-            final float rotation = (float) Math.toDegrees(Math.atan2(sin, cos));
-            final float scaleX = values[Matrix.MSCALE_X] / cos;
-            final float scaleY = values[Matrix.MSCALE_Y] / cos;
-            final float dx = values[Matrix.MTRANS_X];
-            final float dy = values[Matrix.MTRANS_Y];
-            view.setPivotX(0);
-            view.setPivotY(0);
-            view.setTranslationX(dx);
-            view.setTranslationY(dy);
-            view.setRotation(rotation);
-            view.setScaleX(scaleX);
-            view.setScaleY(scaleY);
-        }
-    }
-
-    @Override
-    public void setLeftTopRightBottom(View v, int left, int top, int right, int bottom) {
-        v.setLeft(left);
-        v.setTop(top);
-        v.setRight(right);
-        v.setBottom(bottom);
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/ViewUtilsApi18.java b/transition/src/main/java/androidx/transition/ViewUtilsApi18.java
deleted file mode 100644
index 5ef3b62..0000000
--- a/transition/src/main/java/androidx/transition/ViewUtilsApi18.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(18)
-class ViewUtilsApi18 extends ViewUtilsApi14 {
-
-    @Override
-    public ViewOverlayImpl getOverlay(@NonNull View view) {
-        return new ViewOverlayApi18(view);
-    }
-
-    @Override
-    public WindowIdImpl getWindowId(@NonNull View view) {
-        return new WindowIdApi18(view);
-    }
-
-}
diff --git a/transition/src/main/java/androidx/transition/ViewUtilsApi19.java b/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
index a2f627b..ff60431 100644
--- a/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
+++ b/transition/src/main/java/androidx/transition/ViewUtilsApi19.java
@@ -26,7 +26,7 @@
 import java.lang.reflect.Method;
 
 @RequiresApi(19)
-class ViewUtilsApi19 extends ViewUtilsApi18 {
+class ViewUtilsApi19 extends ViewUtilsBase {
 
     private static final String TAG = "ViewUtilsApi19";
 
diff --git a/transition/src/main/java/androidx/transition/ViewUtilsBase.java b/transition/src/main/java/androidx/transition/ViewUtilsBase.java
new file mode 100644
index 0000000..c3dad8f
--- /dev/null
+++ b/transition/src/main/java/androidx/transition/ViewUtilsBase.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.transition;
+
+import android.graphics.Matrix;
+import android.view.View;
+import android.view.ViewParent;
+
+import androidx.annotation.NonNull;
+
+class ViewUtilsBase {
+
+    private float[] mMatrixValues;
+
+    public void setTransitionAlpha(@NonNull View view, float alpha) {
+        Float savedAlpha = (Float) view.getTag(R.id.save_non_transition_alpha);
+        if (savedAlpha != null) {
+            view.setAlpha(savedAlpha * alpha);
+        } else {
+            view.setAlpha(alpha);
+        }
+    }
+
+    public float getTransitionAlpha(@NonNull View view) {
+        Float savedAlpha = (Float) view.getTag(R.id.save_non_transition_alpha);
+        if (savedAlpha != null) {
+            return view.getAlpha() / savedAlpha;
+        } else {
+            return view.getAlpha();
+        }
+    }
+
+    public void saveNonTransitionAlpha(@NonNull View view) {
+        if (view.getTag(R.id.save_non_transition_alpha) == null) {
+            view.setTag(R.id.save_non_transition_alpha, view.getAlpha());
+        }
+    }
+
+    public void clearNonTransitionAlpha(@NonNull View view) {
+        // We don't clear the saved value when the view is hidden; that's the situation we are
+        // saving this value for.
+        if (view.getVisibility() == View.VISIBLE) {
+            view.setTag(R.id.save_non_transition_alpha, null);
+        }
+    }
+
+    public void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix) {
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View) {
+            final View vp = (View) parent;
+            transformMatrixToGlobal(vp, matrix);
+            matrix.preTranslate(-vp.getScrollX(), -vp.getScrollY());
+        }
+        matrix.preTranslate(view.getLeft(), view.getTop());
+        final Matrix vm = view.getMatrix();
+        if (!vm.isIdentity()) {
+            matrix.preConcat(vm);
+        }
+    }
+
+    public void transformMatrixToLocal(@NonNull View view, @NonNull Matrix matrix) {
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View) {
+            final View vp = (View) parent;
+            transformMatrixToLocal(vp, matrix);
+            matrix.postTranslate(vp.getScrollX(), vp.getScrollY());
+        }
+        matrix.postTranslate(view.getLeft(), view.getTop());
+        final Matrix vm = view.getMatrix();
+        if (!vm.isIdentity()) {
+            final Matrix inverted = new Matrix();
+            if (vm.invert(inverted)) {
+                matrix.postConcat(inverted);
+            }
+        }
+    }
+
+    public void setAnimationMatrix(@NonNull View view, Matrix matrix) {
+        if (matrix == null || matrix.isIdentity()) {
+            view.setPivotX(view.getWidth() / 2);
+            view.setPivotY(view.getHeight() / 2);
+            view.setTranslationX(0);
+            view.setTranslationY(0);
+            view.setScaleX(1);
+            view.setScaleY(1);
+            view.setRotation(0);
+        } else {
+            float[] values = mMatrixValues;
+            if (values == null) {
+                mMatrixValues = values = new float[9];
+            }
+            matrix.getValues(values);
+            final float sin = values[Matrix.MSKEW_Y];
+            final float cos = (float) Math.sqrt(1 - sin * sin)
+                    * (values[Matrix.MSCALE_X] < 0 ? -1 : 1);
+            final float rotation = (float) Math.toDegrees(Math.atan2(sin, cos));
+            final float scaleX = values[Matrix.MSCALE_X] / cos;
+            final float scaleY = values[Matrix.MSCALE_Y] / cos;
+            final float dx = values[Matrix.MTRANS_X];
+            final float dy = values[Matrix.MTRANS_Y];
+            view.setPivotX(0);
+            view.setPivotY(0);
+            view.setTranslationX(dx);
+            view.setTranslationY(dy);
+            view.setRotation(rotation);
+            view.setScaleX(scaleX);
+            view.setScaleY(scaleY);
+        }
+    }
+
+    public void setLeftTopRightBottom(View v, int left, int top, int right, int bottom) {
+        v.setLeft(left);
+        v.setTop(top);
+        v.setRight(right);
+        v.setBottom(bottom);
+    }
+
+}
diff --git a/transition/src/main/java/androidx/transition/ViewUtilsImpl.java b/transition/src/main/java/androidx/transition/ViewUtilsImpl.java
deleted file mode 100644
index 50b4fb4..0000000
--- a/transition/src/main/java/androidx/transition/ViewUtilsImpl.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.transition;
-
-import android.graphics.Matrix;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(14)
-interface ViewUtilsImpl {
-
-    ViewOverlayImpl getOverlay(@NonNull View view);
-
-    WindowIdImpl getWindowId(@NonNull View view);
-
-    void setTransitionAlpha(@NonNull View view, float alpha);
-
-    float getTransitionAlpha(@NonNull View view);
-
-    void saveNonTransitionAlpha(@NonNull View view);
-
-    void clearNonTransitionAlpha(@NonNull View view);
-
-    void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix);
-
-    void transformMatrixToLocal(@NonNull View view, @NonNull Matrix matrix);
-
-    void setAnimationMatrix(@NonNull View view, Matrix matrix);
-
-    void setLeftTopRightBottom(View v, int left, int top, int right, int bottom);
-
-}
diff --git a/transition/src/main/java/androidx/transition/Visibility.java b/transition/src/main/java/androidx/transition/Visibility.java
index 3726bf7..f26a547 100644
--- a/transition/src/main/java/androidx/transition/Visibility.java
+++ b/transition/src/main/java/androidx/transition/Visibility.java
@@ -469,7 +469,7 @@
     }
 
     private static class DisappearListener extends AnimatorListenerAdapter
-            implements TransitionListener, AnimatorUtilsApi14.AnimatorPauseListenerCompat {
+            implements TransitionListener, AnimatorUtils.AnimatorPauseListenerCompat {
 
         private final View mView;
         private final int mFinalVisibility;
diff --git a/v7/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java b/v7/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
index 6502aab..c079f44 100644
--- a/v7/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
+++ b/v7/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
@@ -114,8 +114,8 @@
         // Set MODE_NIGHT_AUTO so that we will change to night mode automatically
         final NightModeActivity newActivity =
                 setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO);
-        final AppCompatDelegateImplV14 newDelegate =
-                (AppCompatDelegateImplV14) newActivity.getDelegate();
+        final AppCompatDelegateImplBase newDelegate =
+                (AppCompatDelegateImplBase) newActivity.getDelegate();
 
         // Update the fake twilight manager to be in night and trigger a fake 'time' change
         mActivityTestRule.runOnUiThread(new Runnable() {
diff --git a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
index d93ca95..b66dec3 100644
--- a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
+++ b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
@@ -200,7 +200,7 @@
         } else if (Build.VERSION.SDK_INT >= 23) {
             return new AppCompatDelegateImplV23(context, window, callback);
         } else {
-            return new AppCompatDelegateImplV14(context, window, callback);
+            return new AppCompatDelegateImplBase(context, window, callback);
         }
     }
 
diff --git a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplBase.java b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplBase.java
index 6be3804..eb4c63e 100644
--- a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplBase.java
+++ b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplBase.java
@@ -16,40 +16,110 @@
 
 package androidx.appcompat.app;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.Window.FEATURE_OPTIONS_PANEL;
+
 import android.app.Activity;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
+import android.widget.PopupWindow;
+import android.widget.TextView;
 
-import androidx.annotation.RequiresApi;
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.R;
+import androidx.appcompat.content.res.AppCompatResources;
 import androidx.appcompat.view.ActionMode;
+import androidx.appcompat.view.ContextThemeWrapper;
+import androidx.appcompat.view.StandaloneActionMode;
+import androidx.appcompat.view.SupportActionModeWrapper;
 import androidx.appcompat.view.SupportMenuInflater;
 import androidx.appcompat.view.WindowCallbackWrapper;
+import androidx.appcompat.view.menu.ListMenuPresenter;
 import androidx.appcompat.view.menu.MenuBuilder;
+import androidx.appcompat.view.menu.MenuPresenter;
+import androidx.appcompat.view.menu.MenuView;
+import androidx.appcompat.widget.ActionBarContextView;
+import androidx.appcompat.widget.AppCompatDrawableManager;
+import androidx.appcompat.widget.ContentFrameLayout;
+import androidx.appcompat.widget.DecorContentParent;
+import androidx.appcompat.widget.FitWindowsViewGroup;
 import androidx.appcompat.widget.TintTypedArray;
+import androidx.appcompat.widget.Toolbar;
+import androidx.appcompat.widget.VectorEnabledTintResources;
+import androidx.appcompat.widget.ViewStubCompat;
+import androidx.appcompat.widget.ViewUtils;
+import androidx.core.app.NavUtils;
+import androidx.core.view.LayoutInflaterCompat;
+import androidx.core.view.OnApplyWindowInsetsListener;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.ViewPropertyAnimatorCompat;
+import androidx.core.view.ViewPropertyAnimatorListenerAdapter;
+import androidx.core.view.WindowCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.core.widget.PopupWindowCompat;
 
-@RequiresApi(14)
-abstract class AppCompatDelegateImplBase extends AppCompatDelegate {
+import org.xmlpull.v1.XmlPullParser;
 
-    static final boolean DEBUG = false;
+class AppCompatDelegateImplBase extends AppCompatDelegate
+        implements MenuBuilder.Callback, LayoutInflater.Factory2 {
+
+    private static final boolean DEBUG = false;
+    private static final boolean IS_PRE_LOLLIPOP = Build.VERSION.SDK_INT < 21;
+    private static final String KEY_LOCAL_NIGHT_MODE = "appcompat:local_night_mode";
+
+    private static final int[] sWindowBackgroundStyleable = {android.R.attr.windowBackground};
 
     private static boolean sInstalledExceptionHandler;
-    private static final boolean SHOULD_INSTALL_EXCEPTION_HANDLER = Build.VERSION.SDK_INT < 21;
 
     static final String EXCEPTION_HANDLER_MESSAGE_SUFFIX= ". If the resource you are"
             + " trying to use is a vector resource, you may be referencing it in an unsupported"
             + " way. See AppCompatDelegate.setCompatVectorFromResourcesEnabled() for more info.";
 
     static {
-        if (SHOULD_INSTALL_EXCEPTION_HANDLER && !sInstalledExceptionHandler) {
+        if (IS_PRE_LOLLIPOP && !sInstalledExceptionHandler) {
             final Thread.UncaughtExceptionHandler defHandler
                     = Thread.getDefaultUncaughtExceptionHandler();
 
@@ -82,8 +152,6 @@
         }
     }
 
-    private static final int[] sWindowBackgroundStyleable = {android.R.attr.windowBackground};
-
     final Context mContext;
     final Window mWindow;
     final Window.Callback mOriginalWindowCallback;
@@ -93,6 +161,30 @@
     ActionBar mActionBar;
     MenuInflater mMenuInflater;
 
+    private CharSequence mTitle;
+
+    private DecorContentParent mDecorContentParent;
+    private ActionMenuPresenterCallback mActionMenuPresenterCallback;
+    private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
+
+    ActionMode mActionMode;
+    ActionBarContextView mActionModeView;
+    PopupWindow mActionModePopup;
+    Runnable mShowActionModePopup;
+    ViewPropertyAnimatorCompat mFadeAnim = null;
+
+    private boolean mHandleNativeActionModes = true; // defaults to true
+
+    // true if we have installed a window sub-decor layout.
+    private boolean mSubDecorInstalled;
+    private ViewGroup mSubDecor;
+
+    private TextView mTitleView;
+    private View mStatusGuard;
+
+    // Used to keep track of Progress Bar Window features
+    private boolean mFeatureProgress, mFeatureIndeterminateProgress;
+
     // true if this activity has an action bar.
     boolean mHasActionBar;
     // true if this activity's action bar overlays other activity content.
@@ -104,11 +196,43 @@
     // true if this activity has no title
     boolean mWindowNoTitle;
 
-    private CharSequence mTitle;
+    // Used for emulating PanelFeatureState
+    private boolean mClosingActionMenu;
+    private PanelFeatureState[] mPanels;
+    private PanelFeatureState mPreparedPanel;
 
-    private boolean mIsStarted;
+    private boolean mLongPressBackDown;
+
     private boolean mIsDestroyed;
-    private boolean mEatKeyUpEvent;
+
+    @NightMode
+    private int mLocalNightMode = MODE_NIGHT_UNSPECIFIED;
+    private boolean mApplyDayNightCalled;
+
+    private AutoNightModeManager mAutoNightModeManager;
+
+    boolean mInvalidatePanelMenuPosted;
+    int mInvalidatePanelMenuFeatures;
+    private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) {
+                doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL);
+            }
+            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_SUPPORT_ACTION_BAR) != 0) {
+                doInvalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
+            }
+            mInvalidatePanelMenuPosted = false;
+            mInvalidatePanelMenuFeatures = 0;
+        }
+    };
+
+    private boolean mEnableDefaultActionBarUp;
+
+    private Rect mTempRect1;
+    private Rect mTempRect2;
+
+    private AppCompatViewInflater mAppCompatViewInflater;
 
     AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) {
         mContext = context;
@@ -116,7 +240,7 @@
         mAppCompatCallback = callback;
 
         mOriginalWindowCallback = mWindow.getCallback();
-        if (mOriginalWindowCallback instanceof AppCompatWindowCallbackBase) {
+        if (mOriginalWindowCallback instanceof AppCompatWindowCallbackV14) {
             throw new IllegalStateException(
                     "AppCompat has already installed itself into the Window");
         }
@@ -133,10 +257,32 @@
         a.recycle();
     }
 
-    abstract void initWindowDecorActionBar();
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        if (mOriginalWindowCallback instanceof Activity) {
+            if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) {
+                // Peek at the Action Bar and update it if it already exists
+                ActionBar ab = peekSupportActionBar();
+                if (ab == null) {
+                    mEnableDefaultActionBarUp = true;
+                } else {
+                    ab.setDefaultDisplayHomeAsUpEnabled(true);
+                }
+            }
+        }
 
-    Window.Callback wrapWindowCallback(Window.Callback callback) {
-        return new AppCompatWindowCallbackBase(callback);
+        if (savedInstanceState != null && mLocalNightMode == MODE_NIGHT_UNSPECIFIED) {
+            // If we have a icicle and we haven't had a local night mode set yet, try and read
+            // it from the icicle
+            mLocalNightMode = savedInstanceState.getInt(KEY_LOCAL_NIGHT_MODE,
+                    MODE_NIGHT_UNSPECIFIED);
+        }
+    }
+
+    @Override
+    public void onPostCreate(Bundle savedInstanceState) {
+        // Make sure that the sub decor is installed
+        ensureSubDecor();
     }
 
     @Override
@@ -151,34 +297,67 @@
         return mActionBar;
     }
 
-    @Override
-    public MenuInflater getMenuInflater() {
-        // Make sure that action views can get an appropriate theme.
-        if (mMenuInflater == null) {
-            initWindowDecorActionBar();
-            mMenuInflater = new SupportMenuInflater(
-                    mActionBar != null ? mActionBar.getThemedContext() : mContext);
+    Window.Callback wrapWindowCallback(Window.Callback callback) {
+        return new AppCompatWindowCallbackV14(callback);
+    }
+
+    final Window.Callback getWindowCallback() {
+        return mWindow.getCallback();
+    }
+
+    private void initWindowDecorActionBar() {
+        ensureSubDecor();
+
+        if (!mHasActionBar || mActionBar != null) {
+            return;
         }
-        return mMenuInflater;
-    }
 
-    // Methods used to create and respond to options menu
-    abstract void onPanelClosed(int featureId, Menu menu);
-
-    abstract boolean onMenuOpened(int featureId, Menu menu);
-
-    abstract boolean dispatchKeyEvent(KeyEvent event);
-
-    abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
-
-    @Override
-    public void setLocalNightMode(@NightMode int mode) {
-        // no-op
+        if (mOriginalWindowCallback instanceof Activity) {
+            mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback,
+                    mOverlayActionBar);
+        } else if (mOriginalWindowCallback instanceof Dialog) {
+            mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback);
+        }
+        if (mActionBar != null) {
+            mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
+        }
     }
 
     @Override
-    public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
-        return new ActionBarDrawableToggleImpl();
+    public void setSupportActionBar(Toolbar toolbar) {
+        if (!(mOriginalWindowCallback instanceof Activity)) {
+            // Only Activities support custom Action Bars
+            return;
+        }
+
+        final ActionBar ab = getSupportActionBar();
+        if (ab instanceof WindowDecorActionBar) {
+            throw new IllegalStateException("This Activity already has an action bar supplied " +
+                    "by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " +
+                    "windowActionBar to false in your theme to use a Toolbar instead.");
+        }
+
+        // If we reach here then we're setting a new action bar
+        // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
+        mMenuInflater = null;
+
+        // If we have an action bar currently, destroy it
+        if (ab != null) {
+            ab.onDestroy();
+        }
+
+        if (toolbar != null) {
+            final ToolbarActionBar tbab = new ToolbarActionBar(toolbar,
+                    ((Activity) mOriginalWindowCallback).getTitle(), mAppCompatWindowCallback);
+            mActionBar = tbab;
+            mWindow.setCallback(tbab.getWrappedWindowCallback());
+        } else {
+            mActionBar = null;
+            // Re-set the original window callback since we may have already set a Toolbar wrapper
+            mWindow.setCallback(mAppCompatWindowCallback);
+        }
+
+        invalidateOptionsMenu();
     }
 
     final Context getActionBarThemedContext() {
@@ -196,107 +375,466 @@
         return context;
     }
 
-    private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate {
-        ActionBarDrawableToggleImpl() {
+    @Override
+    public MenuInflater getMenuInflater() {
+        // Make sure that action views can get an appropriate theme.
+        if (mMenuInflater == null) {
+            initWindowDecorActionBar();
+            mMenuInflater = new SupportMenuInflater(
+                    mActionBar != null ? mActionBar.getThemedContext() : mContext);
         }
-
-        @Override
-        public Drawable getThemeUpIndicator() {
-            final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
-                    getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator });
-            final Drawable result = a.getDrawable(0);
-            a.recycle();
-            return result;
-        }
-
-        @Override
-        public Context getActionBarThemedContext() {
-            return AppCompatDelegateImplBase.this.getActionBarThemedContext();
-        }
-
-        @Override
-        public boolean isNavigationVisible() {
-            final ActionBar ab = getSupportActionBar();
-            return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
-        }
-
-        @Override
-        public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
-            ActionBar ab = getSupportActionBar();
-            if (ab != null) {
-                ab.setHomeAsUpIndicator(upDrawable);
-                ab.setHomeActionContentDescription(contentDescRes);
-            }
-        }
-
-        @Override
-        public void setActionBarDescription(int contentDescRes) {
-            ActionBar ab = getSupportActionBar();
-            if (ab != null) {
-                ab.setHomeActionContentDescription(contentDescRes);
-            }
-        }
+        return mMenuInflater;
     }
 
-    abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback);
+    @SuppressWarnings("TypeParameterUnusedInFormals")
+    @Nullable
+    @Override
+    public <T extends View> T findViewById(@IdRes int id) {
+        ensureSubDecor();
+        return (T) mWindow.findViewById(id);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        // If this is called before sub-decor is installed, ActionBar will not
+        // be properly initialized.
+        if (mHasActionBar && mSubDecorInstalled) {
+            // Note: The action bar will need to access
+            // view changes from superclass.
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.onConfigurationChanged(newConfig);
+            }
+        }
+
+        // Make sure that the DrawableManager knows about the new config
+        AppCompatDrawableManager.get().onConfigurationChanged(mContext);
+
+        // Re-apply Day/Night to the new configuration
+        applyDayNight();
+    }
 
     @Override
     public void onStart() {
-        mIsStarted = true;
+        // This will apply day/night if the time has changed, it will also call through to
+        // setupAutoNightModeIfNeeded()
+        applyDayNight();
     }
 
     @Override
     public void onStop() {
-        mIsStarted = false;
+        ActionBar ab = getSupportActionBar();
+        if (ab != null) {
+            ab.setShowHideAnimationEnabled(false);
+        }
+
+        // Make sure we clean up any receivers setup for AUTO mode
+        if (mAutoNightModeManager != null) {
+            mAutoNightModeManager.cleanup();
+        }
+    }
+
+    @Override
+    public void onPostResume() {
+        ActionBar ab = getSupportActionBar();
+        if (ab != null) {
+            ab.setShowHideAnimationEnabled(true);
+        }
+    }
+
+    @Override
+    public void setContentView(View v) {
+        ensureSubDecor();
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
+        contentParent.removeAllViews();
+        contentParent.addView(v);
+        mOriginalWindowCallback.onContentChanged();
+    }
+
+    @Override
+    public void setContentView(int resId) {
+        ensureSubDecor();
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
+        contentParent.removeAllViews();
+        LayoutInflater.from(mContext).inflate(resId, contentParent);
+        mOriginalWindowCallback.onContentChanged();
+    }
+
+    @Override
+    public void setContentView(View v, ViewGroup.LayoutParams lp) {
+        ensureSubDecor();
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
+        contentParent.removeAllViews();
+        contentParent.addView(v, lp);
+        mOriginalWindowCallback.onContentChanged();
+    }
+
+    @Override
+    public void addContentView(View v, ViewGroup.LayoutParams lp) {
+        ensureSubDecor();
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
+        contentParent.addView(v, lp);
+        mOriginalWindowCallback.onContentChanged();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        if (mLocalNightMode != MODE_NIGHT_UNSPECIFIED) {
+            // If we have a local night mode set, save it
+            outState.putInt(KEY_LOCAL_NIGHT_MODE, mLocalNightMode);
+        }
     }
 
     @Override
     public void onDestroy() {
+        if (mInvalidatePanelMenuPosted) {
+            mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
+        }
+
         mIsDestroyed = true;
+
+        if (mActionBar != null) {
+            mActionBar.onDestroy();
+        }
+
+        // Make sure we clean up any receivers setup for AUTO mode
+        if (mAutoNightModeManager != null) {
+            mAutoNightModeManager.cleanup();
+        }
+    }
+
+    private void ensureSubDecor() {
+        if (!mSubDecorInstalled) {
+            mSubDecor = createSubDecor();
+
+            // If a title was set before we installed the decor, propagate it now
+            CharSequence title = getTitle();
+            if (!TextUtils.isEmpty(title)) {
+                if (mDecorContentParent != null) {
+                    mDecorContentParent.setWindowTitle(title);
+                } else if (peekSupportActionBar() != null) {
+                    peekSupportActionBar().setWindowTitle(title);
+                } else if (mTitleView != null) {
+                    mTitleView.setText(title);
+                }
+            }
+
+            applyFixedSizeWindow();
+
+            onSubDecorInstalled(mSubDecor);
+
+            mSubDecorInstalled = true;
+
+            // Invalidate if the panel menu hasn't been created before this.
+            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
+            // being called in the middle of onCreate or similar.
+            // A pending invalidation will typically be resolved before the posted message
+            // would run normally in order to satisfy instance state restoration.
+            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+            if (!mIsDestroyed && (st == null || st.menu == null)) {
+                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
+            }
+        }
+    }
+
+    private ViewGroup createSubDecor() {
+        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+
+        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
+            a.recycle();
+            throw new IllegalStateException(
+                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
+        }
+
+        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
+            requestWindowFeature(Window.FEATURE_NO_TITLE);
+        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
+            // Don't allow an action bar if there is no title.
+            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
+        }
+        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
+            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
+        }
+        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
+            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
+        }
+        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
+        a.recycle();
+
+        // Now let's make sure that the Window has installed its decor by retrieving it
+        mWindow.getDecorView();
+
+        final LayoutInflater inflater = LayoutInflater.from(mContext);
+        ViewGroup subDecor = null;
+
+
+        if (!mWindowNoTitle) {
+            if (mIsFloating) {
+                // If we're floating, inflate the dialog title decor
+                subDecor = (ViewGroup) inflater.inflate(
+                        R.layout.abc_dialog_title_material, null);
+
+                // Floating windows can never have an action bar, reset the flags
+                mHasActionBar = mOverlayActionBar = false;
+            } else if (mHasActionBar) {
+                /**
+                 * This needs some explanation. As we can not use the android:theme attribute
+                 * pre-L, we emulate it by manually creating a LayoutInflater using a
+                 * ContextThemeWrapper pointing to actionBarTheme.
+                 */
+                TypedValue outValue = new TypedValue();
+                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+                Context themedContext;
+                if (outValue.resourceId != 0) {
+                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
+                } else {
+                    themedContext = mContext;
+                }
+
+                // Now inflate the view using the themed context and set it as the content view
+                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
+                        .inflate(R.layout.abc_screen_toolbar, null);
+
+                mDecorContentParent = (DecorContentParent) subDecor
+                        .findViewById(R.id.decor_content_parent);
+                mDecorContentParent.setWindowCallback(getWindowCallback());
+
+                /**
+                 * Propagate features to DecorContentParent
+                 */
+                if (mOverlayActionBar) {
+                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
+                }
+                if (mFeatureProgress) {
+                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
+                }
+                if (mFeatureIndeterminateProgress) {
+                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+                }
+            }
+        } else {
+            if (mOverlayActionMode) {
+                subDecor = (ViewGroup) inflater.inflate(
+                        R.layout.abc_screen_simple_overlay_action_mode, null);
+            } else {
+                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
+            }
+
+            if (Build.VERSION.SDK_INT >= 21) {
+                // If we're running on L or above, we can rely on ViewCompat's
+                // setOnApplyWindowInsetsListener
+                ViewCompat.setOnApplyWindowInsetsListener(subDecor,
+                        new OnApplyWindowInsetsListener() {
+                            @Override
+                            public WindowInsetsCompat onApplyWindowInsets(View v,
+                                    WindowInsetsCompat insets) {
+                                final int top = insets.getSystemWindowInsetTop();
+                                final int newTop = updateStatusGuard(top);
+
+                                if (top != newTop) {
+                                    insets = insets.replaceSystemWindowInsets(
+                                            insets.getSystemWindowInsetLeft(),
+                                            newTop,
+                                            insets.getSystemWindowInsetRight(),
+                                            insets.getSystemWindowInsetBottom());
+                                }
+
+                                // Now apply the insets on our view
+                                return ViewCompat.onApplyWindowInsets(v, insets);
+                            }
+                        });
+            } else {
+                // Else, we need to use our own FitWindowsViewGroup handling
+                ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
+                        new FitWindowsViewGroup.OnFitSystemWindowsListener() {
+                            @Override
+                            public void onFitSystemWindows(Rect insets) {
+                                insets.top = updateStatusGuard(insets.top);
+                            }
+                        });
+            }
+        }
+
+        if (subDecor == null) {
+            throw new IllegalArgumentException(
+                    "AppCompat does not support the current theme features: { "
+                            + "windowActionBar: " + mHasActionBar
+                            + ", windowActionBarOverlay: "+ mOverlayActionBar
+                            + ", android:windowIsFloating: " + mIsFloating
+                            + ", windowActionModeOverlay: " + mOverlayActionMode
+                            + ", windowNoTitle: " + mWindowNoTitle
+                            + " }");
+        }
+
+        if (mDecorContentParent == null) {
+            mTitleView = (TextView) subDecor.findViewById(R.id.title);
+        }
+
+        // Make the decor optionally fit system windows, like the window's decor
+        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
+
+        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
+                R.id.action_bar_activity_content);
+
+        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
+        if (windowContentView != null) {
+            // There might be Views already added to the Window's content view so we need to
+            // migrate them to our content view
+            while (windowContentView.getChildCount() > 0) {
+                final View child = windowContentView.getChildAt(0);
+                windowContentView.removeViewAt(0);
+                contentView.addView(child);
+            }
+
+            // Change our content FrameLayout to use the android.R.id.content id.
+            // Useful for fragments.
+            windowContentView.setId(View.NO_ID);
+            contentView.setId(android.R.id.content);
+
+            // The decorContent may have a foreground drawable set (windowContentOverlay).
+            // Remove this as we handle it ourselves
+            if (windowContentView instanceof FrameLayout) {
+                ((FrameLayout) windowContentView).setForeground(null);
+            }
+        }
+
+        // Now set the Window's content view with the decor
+        mWindow.setContentView(subDecor);
+
+        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
+            @Override
+            public void onAttachedFromWindow() {}
+
+            @Override
+            public void onDetachedFromWindow() {
+                dismissPopups();
+            }
+        });
+
+        return subDecor;
+    }
+
+    void onSubDecorInstalled(ViewGroup subDecor) {}
+
+    private void applyFixedSizeWindow() {
+        ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);
+
+        // This is a bit weird. In the framework, the window sizing attributes control
+        // the decor view's size, meaning that any padding is inset for the min/max widths below.
+        // We don't control measurement at that level, so we need to workaround it by making sure
+        // that the decor view's padding is taken into account.
+        final View windowDecor = mWindow.getDecorView();
+        cfl.setDecorPadding(windowDecor.getPaddingLeft(),
+                windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
+                windowDecor.getPaddingBottom());
+
+        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
+        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
+
+        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
+            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
+                    cfl.getFixedWidthMajor());
+        }
+        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
+            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
+                    cfl.getFixedWidthMinor());
+        }
+        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
+            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
+                    cfl.getFixedHeightMajor());
+        }
+        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
+            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
+                    cfl.getFixedHeightMinor());
+        }
+        a.recycle();
+
+        cfl.requestLayout();
     }
 
     @Override
-    public void setHandleNativeActionModesEnabled(boolean enabled) {
-        // no-op pre-v14
+    public boolean requestWindowFeature(int featureId) {
+        featureId = sanitizeWindowFeatureId(featureId);
+
+        if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
+            return false; // Ignore. No title dominates.
+        }
+        if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
+            // Remove the action bar feature if we have no title. No title dominates.
+            mHasActionBar = false;
+        }
+
+        switch (featureId) {
+            case FEATURE_SUPPORT_ACTION_BAR:
+                throwFeatureRequestIfSubDecorInstalled();
+                mHasActionBar = true;
+                return true;
+            case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
+                throwFeatureRequestIfSubDecorInstalled();
+                mOverlayActionBar = true;
+                return true;
+            case FEATURE_ACTION_MODE_OVERLAY:
+                throwFeatureRequestIfSubDecorInstalled();
+                mOverlayActionMode = true;
+                return true;
+            case Window.FEATURE_PROGRESS:
+                throwFeatureRequestIfSubDecorInstalled();
+                mFeatureProgress = true;
+                return true;
+            case Window.FEATURE_INDETERMINATE_PROGRESS:
+                throwFeatureRequestIfSubDecorInstalled();
+                mFeatureIndeterminateProgress = true;
+                return true;
+            case Window.FEATURE_NO_TITLE:
+                throwFeatureRequestIfSubDecorInstalled();
+                mWindowNoTitle = true;
+                return true;
+        }
+
+        return mWindow.requestFeature(featureId);
     }
 
     @Override
-    public boolean isHandleNativeActionModesEnabled() {
-        // Always false pre-v14
-        return false;
-    }
-
-    @Override
-    public boolean applyDayNight() {
-        // no-op on v7
-        return false;
-    }
-
-    final boolean isDestroyed() {
-        return mIsDestroyed;
-    }
-
-    final boolean isStarted() {
-        return mIsStarted;
-    }
-
-    final Window.Callback getWindowCallback() {
-        return mWindow.getCallback();
+    public boolean hasWindowFeature(int featureId) {
+        boolean result = false;
+        switch (sanitizeWindowFeatureId(featureId)) {
+            case FEATURE_SUPPORT_ACTION_BAR:
+                result = mHasActionBar;
+                break;
+            case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
+                result = mOverlayActionBar;
+                break;
+            case FEATURE_ACTION_MODE_OVERLAY:
+                result = mOverlayActionMode;
+                break;
+            case Window.FEATURE_PROGRESS:
+                result = mFeatureProgress;
+                break;
+            case Window.FEATURE_INDETERMINATE_PROGRESS:
+                result = mFeatureIndeterminateProgress;
+                break;
+            case Window.FEATURE_NO_TITLE:
+                result = mWindowNoTitle;
+                break;
+        }
+        return result || mWindow.hasFeature(featureId);
     }
 
     @Override
     public final void setTitle(CharSequence title) {
         mTitle = title;
-        onTitleChanged(title);
-    }
 
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        // no-op
+        if (mDecorContentParent != null) {
+            mDecorContentParent.setWindowTitle(title);
+        } else if (peekSupportActionBar() != null) {
+            peekSupportActionBar().setWindowTitle(title);
+        } else if (mTitleView != null) {
+            mTitleView.setText(title);
+        }
     }
 
-    abstract void onTitleChanged(CharSequence title);
-
     final CharSequence getTitle() {
         // If the original window callback is an Activity, we'll use its title
         if (mOriginalWindowCallback instanceof Activity) {
@@ -306,8 +844,1658 @@
         return mTitle;
     }
 
-    class AppCompatWindowCallbackBase extends WindowCallbackWrapper {
-        AppCompatWindowCallbackBase(Window.Callback callback) {
+    void onPanelClosed(final int featureId) {
+        if (featureId == FEATURE_SUPPORT_ACTION_BAR) {
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.dispatchMenuVisibilityChanged(false);
+            }
+        } else if (featureId == FEATURE_OPTIONS_PANEL) {
+            // Make sure that the options panel is closed. This is mainly used when we're using a
+            // ToolbarActionBar
+            PanelFeatureState st = getPanelState(featureId, true);
+            if (st.isOpen) {
+                closePanel(st, false);
+            }
+        }
+    }
+
+    void onMenuOpened(final int featureId) {
+        if (featureId == FEATURE_SUPPORT_ACTION_BAR) {
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.dispatchMenuVisibilityChanged(true);
+            }
+        }
+    }
+
+    @Override
+    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+        final Window.Callback cb = getWindowCallback();
+        if (cb != null && !mIsDestroyed) {
+            final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
+            if (panel != null) {
+                return cb.onMenuItemSelected(panel.featureId, item);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onMenuModeChange(MenuBuilder menu) {
+        reopenMenu(menu, true);
+    }
+
+    @Override
+    public ActionMode startSupportActionMode(@NonNull final ActionMode.Callback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("ActionMode callback can not be null.");
+        }
+
+        if (mActionMode != null) {
+            mActionMode.finish();
+        }
+
+        final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV9(callback);
+
+        ActionBar ab = getSupportActionBar();
+        if (ab != null) {
+            mActionMode = ab.startActionMode(wrappedCallback);
+            if (mActionMode != null && mAppCompatCallback != null) {
+                mAppCompatCallback.onSupportActionModeStarted(mActionMode);
+            }
+        }
+
+        if (mActionMode == null) {
+            // If the action bar didn't provide an action mode, start the emulated window one
+            mActionMode = startSupportActionModeFromWindow(wrappedCallback);
+        }
+
+        return mActionMode;
+    }
+
+    @Override
+    public void invalidateOptionsMenu() {
+        final ActionBar ab = getSupportActionBar();
+        if (ab != null && ab.invalidateOptionsMenu()) return;
+
+        invalidatePanelMenu(FEATURE_OPTIONS_PANEL);
+    }
+
+    ActionMode startSupportActionModeFromWindow(@NonNull ActionMode.Callback callback) {
+        endOnGoingFadeAnimation();
+        if (mActionMode != null) {
+            mActionMode.finish();
+        }
+
+        if (!(callback instanceof ActionModeCallbackWrapperV9)) {
+            // If the callback hasn't been wrapped yet, wrap it
+            callback = new ActionModeCallbackWrapperV9(callback);
+        }
+
+        ActionMode mode = null;
+        if (mAppCompatCallback != null && !mIsDestroyed) {
+            try {
+                mode = mAppCompatCallback.onWindowStartingSupportActionMode(callback);
+            } catch (AbstractMethodError ame) {
+                // Older apps might not implement this callback method.
+            }
+        }
+
+        if (mode != null) {
+            mActionMode = mode;
+        } else {
+            if (mActionModeView == null) {
+                if (mIsFloating) {
+                    // Use the action bar theme.
+                    final TypedValue outValue = new TypedValue();
+                    final Resources.Theme baseTheme = mContext.getTheme();
+                    baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+                    final Context actionBarContext;
+                    if (outValue.resourceId != 0) {
+                        final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
+                        actionBarTheme.setTo(baseTheme);
+                        actionBarTheme.applyStyle(outValue.resourceId, true);
+
+                        actionBarContext = new ContextThemeWrapper(mContext, 0);
+                        actionBarContext.getTheme().setTo(actionBarTheme);
+                    } else {
+                        actionBarContext = mContext;
+                    }
+
+                    mActionModeView = new ActionBarContextView(actionBarContext);
+                    mActionModePopup = new PopupWindow(actionBarContext, null,
+                            R.attr.actionModePopupWindowStyle);
+                    PopupWindowCompat.setWindowLayoutType(mActionModePopup,
+                            WindowManager.LayoutParams.TYPE_APPLICATION);
+                    mActionModePopup.setContentView(mActionModeView);
+                    mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
+
+                    actionBarContext.getTheme().resolveAttribute(
+                            R.attr.actionBarSize, outValue, true);
+                    final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
+                            actionBarContext.getResources().getDisplayMetrics());
+                    mActionModeView.setContentHeight(height);
+                    mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+                    mShowActionModePopup = new Runnable() {
+                        @Override
+                        public void run() {
+                            mActionModePopup.showAtLocation(
+                                    mActionModeView,
+                                    Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+                            endOnGoingFadeAnimation();
+
+                            if (shouldAnimateActionModeView()) {
+                                mActionModeView.setAlpha(0f);
+                                mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
+                                mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
+                                    @Override
+                                    public void onAnimationStart(View view) {
+                                        mActionModeView.setVisibility(View.VISIBLE);
+                                    }
+
+                                    @Override
+                                    public void onAnimationEnd(View view) {
+                                        mActionModeView.setAlpha(1f);
+                                        mFadeAnim.setListener(null);
+                                        mFadeAnim = null;
+                                    }
+                                });
+                            } else {
+                                mActionModeView.setAlpha(1f);
+                                mActionModeView.setVisibility(View.VISIBLE);
+                            }
+                        }
+                    };
+                } else {
+                    ViewStubCompat stub = (ViewStubCompat) mSubDecor
+                            .findViewById(R.id.action_mode_bar_stub);
+                    if (stub != null) {
+                        // Set the layout inflater so that it is inflated with the action bar's context
+                        stub.setLayoutInflater(LayoutInflater.from(getActionBarThemedContext()));
+                        mActionModeView = (ActionBarContextView) stub.inflate();
+                    }
+                }
+            }
+
+            if (mActionModeView != null) {
+                endOnGoingFadeAnimation();
+                mActionModeView.killMode();
+                mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView,
+                        callback, mActionModePopup == null);
+                if (callback.onCreateActionMode(mode, mode.getMenu())) {
+                    mode.invalidate();
+                    mActionModeView.initForMode(mode);
+                    mActionMode = mode;
+
+                    if (shouldAnimateActionModeView()) {
+                        mActionModeView.setAlpha(0f);
+                        mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
+                        mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationStart(View view) {
+                                mActionModeView.setVisibility(View.VISIBLE);
+                                mActionModeView.sendAccessibilityEvent(
+                                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+                                if (mActionModeView.getParent() instanceof View) {
+                                    ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
+                                }
+                            }
+
+                            @Override
+                            public void onAnimationEnd(View view) {
+                                mActionModeView.setAlpha(1f);
+                                mFadeAnim.setListener(null);
+                                mFadeAnim = null;
+                            }
+                        });
+                    } else {
+                        mActionModeView.setAlpha(1f);
+                        mActionModeView.setVisibility(View.VISIBLE);
+                        mActionModeView.sendAccessibilityEvent(
+                                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+                        if (mActionModeView.getParent() instanceof View) {
+                            ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
+                        }
+                    }
+
+                    if (mActionModePopup != null) {
+                        mWindow.getDecorView().post(mShowActionModePopup);
+                    }
+                } else {
+                    mActionMode = null;
+                }
+            }
+        }
+        if (mActionMode != null && mAppCompatCallback != null) {
+            mAppCompatCallback.onSupportActionModeStarted(mActionMode);
+        }
+        return mActionMode;
+    }
+
+    final boolean shouldAnimateActionModeView() {
+        // We only to animate the action mode in if the sub decor has already been laid out.
+        // If it hasn't been laid out, it hasn't been drawn to screen yet.
+        return mSubDecorInstalled && mSubDecor != null && ViewCompat.isLaidOut(mSubDecor);
+    }
+
+    @Override
+    public void setHandleNativeActionModesEnabled(boolean enabled) {
+        mHandleNativeActionModes = enabled;
+    }
+
+    @Override
+    public boolean isHandleNativeActionModesEnabled() {
+        return mHandleNativeActionModes;
+    }
+
+    void endOnGoingFadeAnimation() {
+        if (mFadeAnim != null) {
+            mFadeAnim.cancel();
+        }
+    }
+
+    boolean onBackPressed() {
+        // Back cancels action modes first.
+        if (mActionMode != null) {
+            mActionMode.finish();
+            return true;
+        }
+
+        // Next collapse any expanded action views.
+        ActionBar ab = getSupportActionBar();
+        if (ab != null && ab.collapseActionView()) {
+            return true;
+        }
+
+        // Let the call through...
+        return false;
+    }
+
+    boolean onKeyShortcut(int keyCode, KeyEvent ev) {
+        // Let the Action Bar have a chance at handling the shortcut
+        ActionBar ab = getSupportActionBar();
+        if (ab != null && ab.onKeyShortcut(keyCode, ev)) {
+            return true;
+        }
+
+        // If the panel is already prepared, then perform the shortcut using it.
+        boolean handled;
+        if (mPreparedPanel != null) {
+            handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev,
+                    Menu.FLAG_PERFORM_NO_CLOSE);
+            if (handled) {
+                if (mPreparedPanel != null) {
+                    mPreparedPanel.isHandled = true;
+                }
+                return true;
+            }
+        }
+
+        // If the panel is not prepared, then we may be trying to handle a shortcut key
+        // combination such as Control+C.  Temporarily prepare the panel then mark it
+        // unprepared again when finished to ensure that the panel will again be prepared
+        // the next time it is shown for real.
+        if (mPreparedPanel == null) {
+            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
+            preparePanel(st, ev);
+            handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE);
+            st.isPrepared = false;
+            if (handled) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean dispatchKeyEvent(KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
+            // If this is a MENU event, let the Activity have a go.
+            if (mOriginalWindowCallback.dispatchKeyEvent(event)) {
+                return true;
+            }
+        }
+
+        final int keyCode = event.getKeyCode();
+        final int action = event.getAction();
+        final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+        return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event);
+    }
+
+    boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event);
+                return true;
+            case KeyEvent.KEYCODE_BACK:
+                final boolean wasLongPressBackDown = mLongPressBackDown;
+                mLongPressBackDown = false;
+
+                PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+                if (st != null && st.isOpen) {
+                    if (!wasLongPressBackDown) {
+                        // Certain devices allow opening the options menu via a long press of the
+                        // back button. We should only close the open options menu if it wasn't
+                        // opened via a long press gesture.
+                        closePanel(st, true);
+                    }
+                    return true;
+                }
+                if (onBackPressed()) {
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+
+    boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event);
+                // We need to return true here and not let it bubble up to the Window.
+                // For empty menus, PhoneWindow's KEYCODE_BACK handling will steals all events,
+                // not allowing the Activity to call onBackPressed().
+                return true;
+            case KeyEvent.KEYCODE_BACK:
+                // Certain devices allow opening the options menu via a long press of the back
+                // button. We keep a record of whether the last event is from a long press.
+                mLongPressBackDown = (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public View createView(View parent, final String name, @NonNull Context context,
+            @NonNull AttributeSet attrs) {
+        if (mAppCompatViewInflater == null) {
+            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+            String viewInflaterClassName =
+                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
+            if ((viewInflaterClassName == null)
+                    || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
+                // Either default class name or set explicitly to null. In both cases
+                // create the base inflater (no reflection)
+                mAppCompatViewInflater = new AppCompatViewInflater();
+            } else {
+                try {
+                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
+                    mAppCompatViewInflater =
+                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
+                                    .newInstance();
+                } catch (Throwable t) {
+                    Log.i(TAG, "Failed to instantiate custom view inflater "
+                            + viewInflaterClassName + ". Falling back to default.", t);
+                    mAppCompatViewInflater = new AppCompatViewInflater();
+                }
+            }
+        }
+
+        boolean inheritContext = false;
+        if (IS_PRE_LOLLIPOP) {
+            inheritContext = (attrs instanceof XmlPullParser)
+                    // If we have a XmlPullParser, we can detect where we are in the layout
+                    ? ((XmlPullParser) attrs).getDepth() > 1
+                    // Otherwise we have to use the old heuristic
+                    : shouldInheritContext((ViewParent) parent);
+        }
+
+        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
+                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
+                true, /* Read read app:theme as a fallback at all times for legacy reasons */
+                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
+        );
+    }
+
+    private boolean shouldInheritContext(ViewParent parent) {
+        if (parent == null) {
+            // The initial parent is null so just return false
+            return false;
+        }
+        final View windowDecor = mWindow.getDecorView();
+        while (true) {
+            if (parent == null) {
+                // Bingo. We've hit a view which has a null parent before being terminated from
+                // the loop. This is (most probably) because it's the root view in an inflation
+                // call, therefore we should inherit. This works as the inflated layout is only
+                // added to the hierarchy at the end of the inflate() call.
+                return true;
+            } else if (parent == windowDecor || !(parent instanceof View)
+                    || ViewCompat.isAttachedToWindow((View) parent)) {
+                // We have either hit the window's decor view, a parent which isn't a View
+                // (i.e. ViewRootImpl), or an attached view, so we know that the original parent
+                // is currently added to the view hierarchy. This means that it has not be
+                // inflated in the current inflate() call and we should not inherit the context.
+                return false;
+            }
+            parent = parent.getParent();
+        }
+    }
+
+    @Override
+    public void installViewFactory() {
+        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        if (layoutInflater.getFactory() == null) {
+            LayoutInflaterCompat.setFactory2(layoutInflater, this);
+        } else {
+            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplBase)) {
+                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+                        + " so we can not install AppCompat's");
+            }
+        }
+    }
+
+    /**
+     * From {@link LayoutInflater.Factory2}.
+     */
+    @Override
+    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+        return createView(parent, name, context, attrs);
+    }
+
+    /**
+     * From {@link LayoutInflater.Factory2}.
+     */
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        return onCreateView(null, name, context, attrs);
+    }
+
+    private void openPanel(final PanelFeatureState st, KeyEvent event) {
+        // Already open, return
+        if (st.isOpen || mIsDestroyed) {
+            return;
+        }
+
+        // Don't open an options panel on xlarge devices.
+        // (The app should be using an action bar for menu items.)
+        if (st.featureId == FEATURE_OPTIONS_PANEL) {
+            Configuration config = mContext.getResources().getConfiguration();
+            boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
+                    == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+            if (isXLarge) {
+                return;
+            }
+        }
+
+        Window.Callback cb = getWindowCallback();
+        if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
+            // Callback doesn't want the menu to open, reset any state
+            closePanel(st, true);
+            return;
+        }
+
+        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) {
+            return;
+        }
+
+        // Prepare panel (should have been done before, but just in case)
+        if (!preparePanel(st, event)) {
+            return;
+        }
+
+        int width = WRAP_CONTENT;
+        if (st.decorView == null || st.refreshDecorView) {
+            if (st.decorView == null) {
+                // Initialize the panel decor, this will populate st.decorView
+                if (!initializePanelDecor(st) || (st.decorView == null))
+                    return;
+            } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
+                // Decor needs refreshing, so remove its views
+                st.decorView.removeAllViews();
+            }
+
+            // This will populate st.shownPanelView
+            if (!initializePanelContent(st) || !st.hasPanelItems()) {
+                return;
+            }
+
+            ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
+            if (lp == null) {
+                lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+            }
+
+            int backgroundResId = st.background;
+            st.decorView.setBackgroundResource(backgroundResId);
+
+            ViewParent shownPanelParent = st.shownPanelView.getParent();
+            if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
+                ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
+            }
+            st.decorView.addView(st.shownPanelView, lp);
+
+            /*
+             * Give focus to the view, if it or one of its children does not
+             * already have it.
+             */
+            if (!st.shownPanelView.hasFocus()) {
+                st.shownPanelView.requestFocus();
+            }
+        } else if (st.createdPanelView != null) {
+            // If we already had a panel view, carry width=MATCH_PARENT through
+            // as we did above when it was created.
+            ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
+            if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+                width = MATCH_PARENT;
+            }
+        }
+
+        st.isHandled = false;
+
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                width, WRAP_CONTENT,
+                st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                PixelFormat.TRANSLUCENT);
+
+        lp.gravity = st.gravity;
+        lp.windowAnimations = st.windowAnimations;
+
+        wm.addView(st.decorView, lp);
+        st.isOpen = true;
+    }
+
+    private boolean initializePanelDecor(PanelFeatureState st) {
+        st.setStyle(getActionBarThemedContext());
+        st.decorView = new ListMenuDecorView(st.listPresenterContext);
+        st.gravity = Gravity.CENTER | Gravity.BOTTOM;
+        return true;
+    }
+
+    private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) {
+        if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu()
+                && (!ViewConfiguration.get(mContext).hasPermanentMenuKey()
+                        || mDecorContentParent.isOverflowMenuShowPending())) {
+
+            final Window.Callback cb = getWindowCallback();
+
+            if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
+                if (cb != null && !mIsDestroyed) {
+                    // If we have a menu invalidation pending, do it now.
+                    if (mInvalidatePanelMenuPosted &&
+                            (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) {
+                        mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
+                        mInvalidatePanelMenuRunnable.run();
+                    }
+
+                    final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
+
+                    // If we don't have a menu or we're waiting for a full content refresh,
+                    // forget it. This is a lingering event that no longer matters.
+                    if (st.menu != null && !st.refreshMenuContent &&
+                            cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
+                        cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, st.menu);
+                        mDecorContentParent.showOverflowMenu();
+                    }
+                }
+            } else {
+                mDecorContentParent.hideOverflowMenu();
+                if (!mIsDestroyed) {
+                    final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
+                    cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, st.menu);
+                }
+            }
+            return;
+        }
+
+        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
+
+        st.refreshDecorView = true;
+        closePanel(st, false);
+
+        openPanel(st, null);
+    }
+
+    private boolean initializePanelMenu(final PanelFeatureState st) {
+        Context context = mContext;
+
+        // If we have an action bar, initialize the menu with the right theme.
+        if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR) &&
+                mDecorContentParent != null) {
+            final TypedValue outValue = new TypedValue();
+            final Resources.Theme baseTheme = context.getTheme();
+            baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+            Resources.Theme widgetTheme = null;
+            if (outValue.resourceId != 0) {
+                widgetTheme = context.getResources().newTheme();
+                widgetTheme.setTo(baseTheme);
+                widgetTheme.applyStyle(outValue.resourceId, true);
+                widgetTheme.resolveAttribute(
+                        R.attr.actionBarWidgetTheme, outValue, true);
+            } else {
+                baseTheme.resolveAttribute(
+                        R.attr.actionBarWidgetTheme, outValue, true);
+            }
+
+            if (outValue.resourceId != 0) {
+                if (widgetTheme == null) {
+                    widgetTheme = context.getResources().newTheme();
+                    widgetTheme.setTo(baseTheme);
+                }
+                widgetTheme.applyStyle(outValue.resourceId, true);
+            }
+
+            if (widgetTheme != null) {
+                context = new ContextThemeWrapper(context, 0);
+                context.getTheme().setTo(widgetTheme);
+            }
+        }
+
+        final MenuBuilder menu = new MenuBuilder(context);
+        menu.setCallback(this);
+        st.setMenu(menu);
+
+        return true;
+    }
+
+    private boolean initializePanelContent(PanelFeatureState st) {
+        if (st.createdPanelView != null) {
+            st.shownPanelView = st.createdPanelView;
+            return true;
+        }
+
+        if (st.menu == null) {
+            return false;
+        }
+
+        if (mPanelMenuPresenterCallback == null) {
+            mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
+        }
+
+        MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback);
+
+        st.shownPanelView = (View) menuView;
+
+        return st.shownPanelView != null;
+    }
+
+    private boolean preparePanel(PanelFeatureState st, KeyEvent event) {
+        if (mIsDestroyed) {
+            return false;
+        }
+
+        // Already prepared (isPrepared will be reset to false later)
+        if (st.isPrepared) {
+            return true;
+        }
+
+        if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
+            // Another Panel is prepared and possibly open, so close it
+            closePanel(mPreparedPanel, false);
+        }
+
+        final Window.Callback cb = getWindowCallback();
+
+        if (cb != null) {
+            st.createdPanelView = cb.onCreatePanelView(st.featureId);
+        }
+
+        final boolean isActionBarMenu =
+                (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR);
+
+        if (isActionBarMenu && mDecorContentParent != null) {
+            // Enforce ordering guarantees around events so that the action bar never
+            // dispatches menu-related events before the panel is prepared.
+            mDecorContentParent.setMenuPrepared();
+        }
+
+        if (st.createdPanelView == null &&
+                (!isActionBarMenu || !(peekSupportActionBar() instanceof ToolbarActionBar))) {
+            // Since ToolbarActionBar handles the list options menu itself, we only want to
+            // init this menu panel if we're not using a TAB.
+            if (st.menu == null || st.refreshMenuContent) {
+                if (st.menu == null) {
+                    if (!initializePanelMenu(st) || (st.menu == null)) {
+                        return false;
+                    }
+                }
+
+                if (isActionBarMenu && mDecorContentParent != null) {
+                    if (mActionMenuPresenterCallback == null) {
+                        mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+                    }
+                    mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
+                }
+
+                // Creating the panel menu will involve a lot of manipulation;
+                // don't dispatch change events to presenters until we're done.
+                st.menu.stopDispatchingItemsChanged();
+                if (!cb.onCreatePanelMenu(st.featureId, st.menu)) {
+                    // Ditch the menu created above
+                    st.setMenu(null);
+
+                    if (isActionBarMenu && mDecorContentParent != null) {
+                        // Don't show it in the action bar either
+                        mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+                    }
+
+                    return false;
+                }
+
+                st.refreshMenuContent = false;
+            }
+
+            // Preparing the panel menu can involve a lot of manipulation;
+            // don't dispatch change events to presenters until we're done.
+            st.menu.stopDispatchingItemsChanged();
+
+            // Restore action view state before we prepare. This gives apps
+            // an opportunity to override frozen/restored state in onPrepare.
+            if (st.frozenActionViewState != null) {
+                st.menu.restoreActionViewStates(st.frozenActionViewState);
+                st.frozenActionViewState = null;
+            }
+
+            // Callback and return if the callback does not want to show the menu
+            if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
+                if (isActionBarMenu && mDecorContentParent != null) {
+                    // The app didn't want to show the menu for now but it still exists.
+                    // Clear it out of the action bar.
+                    mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+                }
+                st.menu.startDispatchingItemsChanged();
+                return false;
+            }
+
+            // Set the proper keymap
+            KeyCharacterMap kmap = KeyCharacterMap.load(
+                    event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
+            st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
+            st.menu.setQwertyMode(st.qwertyMode);
+            st.menu.startDispatchingItemsChanged();
+        }
+
+        // Set other state
+        st.isPrepared = true;
+        st.isHandled = false;
+        mPreparedPanel = st;
+
+        return true;
+    }
+
+    void checkCloseActionMenu(MenuBuilder menu) {
+        if (mClosingActionMenu) {
+            return;
+        }
+
+        mClosingActionMenu = true;
+        mDecorContentParent.dismissPopups();
+        Window.Callback cb = getWindowCallback();
+        if (cb != null && !mIsDestroyed) {
+            cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, menu);
+        }
+        mClosingActionMenu = false;
+    }
+
+    void closePanel(int featureId) {
+        closePanel(getPanelState(featureId, true), true);
+    }
+
+    void closePanel(PanelFeatureState st, boolean doCallback) {
+        if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
+                mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
+            checkCloseActionMenu(st.menu);
+            return;
+        }
+
+        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        if (wm != null && st.isOpen && st.decorView != null) {
+            wm.removeView(st.decorView);
+
+            if (doCallback) {
+                callOnPanelClosed(st.featureId, st, null);
+            }
+        }
+
+        st.isPrepared = false;
+        st.isHandled = false;
+        st.isOpen = false;
+
+        // This view is no longer shown, so null it out
+        st.shownPanelView = null;
+
+        // Next time the menu opens, it should not be in expanded mode, so
+        // force a refresh of the decor
+        st.refreshDecorView = true;
+
+        if (mPreparedPanel == st) {
+            mPreparedPanel = null;
+        }
+    }
+
+    private boolean onKeyDownPanel(int featureId, KeyEvent event) {
+        if (event.getRepeatCount() == 0) {
+            PanelFeatureState st = getPanelState(featureId, true);
+            if (!st.isOpen) {
+                return preparePanel(st, event);
+            }
+        }
+
+        return false;
+    }
+
+    private boolean onKeyUpPanel(int featureId, KeyEvent event) {
+        if (mActionMode != null) {
+            return false;
+        }
+
+        boolean handled = false;
+        final PanelFeatureState st = getPanelState(featureId, true);
+        if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+                mDecorContentParent.canShowOverflowMenu() &&
+                !ViewConfiguration.get(mContext).hasPermanentMenuKey()) {
+            if (!mDecorContentParent.isOverflowMenuShowing()) {
+                if (!mIsDestroyed && preparePanel(st, event)) {
+                    handled = mDecorContentParent.showOverflowMenu();
+                }
+            } else {
+                handled = mDecorContentParent.hideOverflowMenu();
+            }
+        } else {
+            if (st.isOpen || st.isHandled) {
+                // Play the sound effect if the user closed an open menu (and not if
+                // they just released a menu shortcut)
+                handled = st.isOpen;
+                // Close menu
+                closePanel(st, true);
+            } else if (st.isPrepared) {
+                boolean show = true;
+                if (st.refreshMenuContent) {
+                    // Something may have invalidated the menu since we prepared it.
+                    // Re-prepare it to refresh.
+                    st.isPrepared = false;
+                    show = preparePanel(st, event);
+                }
+
+                if (show) {
+                    // Show menu
+                    openPanel(st, event);
+                    handled = true;
+                }
+            }
+        }
+
+        if (handled) {
+            AudioManager audioManager = (AudioManager) mContext.getSystemService(
+                    Context.AUDIO_SERVICE);
+            if (audioManager != null) {
+                audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+            } else {
+                Log.w(TAG, "Couldn't get audio manager");
+            }
+        }
+        return handled;
+    }
+
+    void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
+        // Try to get a menu
+        if (menu == null) {
+            // Need a panel to grab the menu, so try to get that
+            if (panel == null) {
+                if ((featureId >= 0) && (featureId < mPanels.length)) {
+                    panel = mPanels[featureId];
+                }
+            }
+
+            if (panel != null) {
+                // menu still may be null, which is okay--we tried our best
+                menu = panel.menu;
+            }
+        }
+
+        // If the panel is not open, do not callback
+        if ((panel != null) && (!panel.isOpen))
+            return;
+
+        if (!mIsDestroyed) {
+            // We need to be careful which callback we dispatch the call to. We can not dispatch
+            // this to the Window's callback since that will call back into this method and cause a
+            // crash. Instead we need to dispatch down to the original Activity/Dialog/etc.
+            mOriginalWindowCallback.onPanelClosed(featureId, menu);
+        }
+    }
+
+    PanelFeatureState findMenuPanel(Menu menu) {
+        final PanelFeatureState[] panels = mPanels;
+        final int N = panels != null ? panels.length : 0;
+        for (int i = 0; i < N; i++) {
+            final PanelFeatureState panel = panels[i];
+            if (panel != null && panel.menu == menu) {
+                return panel;
+            }
+        }
+        return null;
+    }
+
+    protected PanelFeatureState getPanelState(int featureId, boolean required) {
+        PanelFeatureState[] ar;
+        if ((ar = mPanels) == null || ar.length <= featureId) {
+            PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
+            if (ar != null) {
+                System.arraycopy(ar, 0, nar, 0, ar.length);
+            }
+            mPanels = ar = nar;
+        }
+
+        PanelFeatureState st = ar[featureId];
+        if (st == null) {
+            ar[featureId] = st = new PanelFeatureState(featureId);
+        }
+        return st;
+    }
+
+    private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+            int flags) {
+        if (event.isSystem()) {
+            return false;
+        }
+
+        boolean handled = false;
+
+        // Only try to perform menu shortcuts if preparePanel returned true (possible false
+        // return value from application not wanting to show the menu).
+        if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
+            // The menu is prepared now, perform the shortcut on it
+            handled = st.menu.performShortcut(keyCode, event, flags);
+        }
+
+        if (handled) {
+            // Only close down the menu if we don't have an action bar keeping it open.
+            if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) {
+                closePanel(st, true);
+            }
+        }
+
+        return handled;
+    }
+
+    private void invalidatePanelMenu(int featureId) {
+        mInvalidatePanelMenuFeatures |= 1 << featureId;
+
+        if (!mInvalidatePanelMenuPosted) {
+            ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable);
+            mInvalidatePanelMenuPosted = true;
+        }
+    }
+
+    void doInvalidatePanelMenu(int featureId) {
+        PanelFeatureState st = getPanelState(featureId, true);
+        Bundle savedActionViewStates = null;
+        if (st.menu != null) {
+            savedActionViewStates = new Bundle();
+            st.menu.saveActionViewStates(savedActionViewStates);
+            if (savedActionViewStates.size() > 0) {
+                st.frozenActionViewState = savedActionViewStates;
+            }
+            // This will be started again when the panel is prepared.
+            st.menu.stopDispatchingItemsChanged();
+            st.menu.clear();
+        }
+        st.refreshMenuContent = true;
+        st.refreshDecorView = true;
+
+        // Prepare the options panel if we have an action bar
+        if ((featureId == FEATURE_SUPPORT_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
+                && mDecorContentParent != null) {
+            st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+            if (st != null) {
+                st.isPrepared = false;
+                preparePanel(st, null);
+            }
+        }
+    }
+
+    /**
+     * Updates the status bar guard
+     *
+     * @param insetTop the current top system window inset
+     * @return the new top system window inset
+     */
+    int updateStatusGuard(int insetTop) {
+        boolean showStatusGuard = false;
+        // Show the status guard when the non-overlay contextual action bar is showing
+        if (mActionModeView != null) {
+            if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+                ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)
+                        mActionModeView.getLayoutParams();
+                boolean mlpChanged = false;
+
+                if (mActionModeView.isShown()) {
+                    if (mTempRect1 == null) {
+                        mTempRect1 = new Rect();
+                        mTempRect2 = new Rect();
+                    }
+                    final Rect insets = mTempRect1;
+                    final Rect localInsets = mTempRect2;
+                    insets.set(0, insetTop, 0, 0);
+
+                    ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets);
+                    final int newMargin = localInsets.top == 0 ? insetTop : 0;
+                    if (mlp.topMargin != newMargin) {
+                        mlpChanged = true;
+                        mlp.topMargin = insetTop;
+
+                        if (mStatusGuard == null) {
+                            mStatusGuard = new View(mContext);
+                            mStatusGuard.setBackgroundColor(mContext.getResources()
+                                    .getColor(R.color.abc_input_method_navigation_guard));
+                            mSubDecor.addView(mStatusGuard, -1,
+                                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                                            insetTop));
+                        } else {
+                            ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams();
+                            if (lp.height != insetTop) {
+                                lp.height = insetTop;
+                                mStatusGuard.setLayoutParams(lp);
+                            }
+                        }
+                    }
+
+                    // The action mode's theme may differ from the app, so
+                    // always show the status guard above it.
+                    showStatusGuard = mStatusGuard != null;
+
+                    // We only need to consume the insets if the action
+                    // mode is overlaid on the app content (e.g. it's
+                    // sitting in a FrameLayout, see
+                    // screen_simple_overlay_action_mode.xml).
+                    if (!mOverlayActionMode && showStatusGuard) {
+                        insetTop = 0;
+                    }
+                } else {
+                    // reset top margin
+                    if (mlp.topMargin != 0) {
+                        mlpChanged = true;
+                        mlp.topMargin = 0;
+                    }
+                }
+                if (mlpChanged) {
+                    mActionModeView.setLayoutParams(mlp);
+                }
+            }
+        }
+        if (mStatusGuard != null) {
+            mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
+        }
+
+        return insetTop;
+    }
+
+    private void throwFeatureRequestIfSubDecorInstalled() {
+        if (mSubDecorInstalled) {
+            throw new AndroidRuntimeException(
+                    "Window feature must be requested before adding content");
+        }
+    }
+
+    private int sanitizeWindowFeatureId(int featureId) {
+        if (featureId == WindowCompat.FEATURE_ACTION_BAR) {
+            Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR"
+                    + " id when requesting this feature.");
+            return FEATURE_SUPPORT_ACTION_BAR;
+        } else if (featureId == WindowCompat.FEATURE_ACTION_BAR_OVERLAY) {
+            Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR_OVERLAY"
+                    + " id when requesting this feature.");
+            return FEATURE_SUPPORT_ACTION_BAR_OVERLAY;
+        }
+        // Else we'll just return the original id
+        return featureId;
+    }
+
+    ViewGroup getSubDecor() {
+        return mSubDecor;
+    }
+
+    void dismissPopups() {
+        if (mDecorContentParent != null) {
+            mDecorContentParent.dismissPopups();
+        }
+
+        if (mActionModePopup != null) {
+            mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
+            if (mActionModePopup.isShowing()) {
+                try {
+                    mActionModePopup.dismiss();
+                } catch (IllegalArgumentException e) {
+                    // Pre-v18, there are times when the Window will remove the popup before us.
+                    // In these cases we need to swallow the resulting exception.
+                }
+            }
+            mActionModePopup = null;
+        }
+        endOnGoingFadeAnimation();
+
+        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+        if (st != null && st.menu != null) {
+            st.menu.close();
+        }
+    }
+
+    @Override
+    public boolean applyDayNight() {
+        boolean applied = false;
+
+        @NightMode final int nightMode = getNightMode();
+        @ApplyableNightMode final int modeToApply = mapNightMode(nightMode);
+        if (modeToApply != MODE_NIGHT_FOLLOW_SYSTEM) {
+            applied = updateForNightMode(modeToApply);
+        }
+
+        if (nightMode == MODE_NIGHT_AUTO) {
+            // If we're already been started, we may need to setup auto mode again
+            ensureAutoNightModeManager();
+            mAutoNightModeManager.setup();
+        }
+
+        mApplyDayNightCalled = true;
+        return applied;
+    }
+
+    @Override
+    public void setLocalNightMode(@NightMode final int mode) {
+        switch (mode) {
+            case MODE_NIGHT_AUTO:
+            case MODE_NIGHT_NO:
+            case MODE_NIGHT_YES:
+            case MODE_NIGHT_FOLLOW_SYSTEM:
+                if (mLocalNightMode != mode) {
+                    mLocalNightMode = mode;
+                    if (mApplyDayNightCalled) {
+                        // If we've already applied day night, re-apply since we won't be
+                        // called again
+                        applyDayNight();
+                    }
+                }
+                break;
+            default:
+                Log.i(TAG, "setLocalNightMode() called with an unknown mode");
+                break;
+        }
+    }
+
+    @ApplyableNightMode
+    int mapNightMode(@NightMode final int mode) {
+        switch (mode) {
+            case MODE_NIGHT_AUTO:
+                ensureAutoNightModeManager();
+                return mAutoNightModeManager.getApplyableNightMode();
+            case MODE_NIGHT_UNSPECIFIED:
+                // If we don't have a mode specified, just let the system handle it
+                return MODE_NIGHT_FOLLOW_SYSTEM;
+            default:
+                return mode;
+        }
+    }
+
+    @NightMode
+    private int getNightMode() {
+        return mLocalNightMode != MODE_NIGHT_UNSPECIFIED ? mLocalNightMode : getDefaultNightMode();
+    }
+
+    /**
+     * Updates the {@link Resources} configuration {@code uiMode} with the
+     * chosen {@code UI_MODE_NIGHT} value.
+     */
+    private boolean updateForNightMode(@ApplyableNightMode final int mode) {
+        final Resources res = mContext.getResources();
+        final Configuration conf = res.getConfiguration();
+        final int currentNightMode = conf.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+        final int newNightMode = (mode == MODE_NIGHT_YES)
+                ? Configuration.UI_MODE_NIGHT_YES
+                : Configuration.UI_MODE_NIGHT_NO;
+
+        if (currentNightMode != newNightMode) {
+            if (shouldRecreateOnNightModeChange()) {
+                if (DEBUG) {
+                    Log.d(TAG, "applyNightMode() | Night mode changed, recreating Activity");
+                }
+                // If we've already been created, we need to recreate the Activity for the
+                // mode to be applied
+                final Activity activity = (Activity) mContext;
+                activity.recreate();
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "applyNightMode() | Night mode changed, updating configuration");
+                }
+                final Configuration config = new Configuration(conf);
+                final DisplayMetrics metrics = res.getDisplayMetrics();
+
+                // Update the UI Mode to reflect the new night mode
+                config.uiMode = newNightMode | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+                res.updateConfiguration(config, metrics);
+
+                // We may need to flush the Resources' drawable cache due to framework bugs.
+                if (!(Build.VERSION.SDK_INT >= 26)) {
+                    ResourcesFlusher.flush(res);
+                }
+            }
+            return true;
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "applyNightMode() | Skipping. Night mode has not changed: " + mode);
+            }
+        }
+        return false;
+    }
+
+    private void ensureAutoNightModeManager() {
+        if (mAutoNightModeManager == null) {
+            mAutoNightModeManager = new AutoNightModeManager(TwilightManager.getInstance(mContext));
+        }
+    }
+
+    @VisibleForTesting
+    final AutoNightModeManager getAutoNightModeManager() {
+        ensureAutoNightModeManager();
+        return mAutoNightModeManager;
+    }
+
+    private boolean shouldRecreateOnNightModeChange() {
+        if (mApplyDayNightCalled && mContext instanceof Activity) {
+            // If we've already applyDayNight() (via setTheme), we need to check if the
+            // Activity has configChanges set to handle uiMode changes
+            final PackageManager pm = mContext.getPackageManager();
+            try {
+                final ActivityInfo info = pm.getActivityInfo(
+                        new ComponentName(mContext, mContext.getClass()), 0);
+                // We should return true (to recreate) if configChanges does not want to
+                // handle uiMode
+                return (info.configChanges & ActivityInfo.CONFIG_UI_MODE) == 0;
+            } catch (PackageManager.NameNotFoundException e) {
+                // This shouldn't happen but let's not crash because of it, we'll just log and
+                // return true (since most apps will do that anyway)
+                Log.d(TAG, "Exception while getting ActivityInfo", e);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Clears out internal reference when the action mode is destroyed.
+     */
+    class ActionModeCallbackWrapperV9 implements ActionMode.Callback {
+        private ActionMode.Callback mWrapped;
+
+        public ActionModeCallbackWrapperV9(ActionMode.Callback wrapped) {
+            mWrapped = wrapped;
+        }
+
+        @Override
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            return mWrapped.onCreateActionMode(mode, menu);
+        }
+
+        @Override
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            return mWrapped.onPrepareActionMode(mode, menu);
+        }
+
+        @Override
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return mWrapped.onActionItemClicked(mode, item);
+        }
+
+        @Override
+        public void onDestroyActionMode(ActionMode mode) {
+            mWrapped.onDestroyActionMode(mode);
+            if (mActionModePopup != null) {
+                mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
+            }
+
+            if (mActionModeView != null) {
+                endOnGoingFadeAnimation();
+                mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f);
+                mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(View view) {
+                        mActionModeView.setVisibility(View.GONE);
+                        if (mActionModePopup != null) {
+                            mActionModePopup.dismiss();
+                        } else if (mActionModeView.getParent() instanceof View) {
+                            ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
+                        }
+                        mActionModeView.removeAllViews();
+                        mFadeAnim.setListener(null);
+                        mFadeAnim = null;
+                    }
+                });
+            }
+            if (mAppCompatCallback != null) {
+                mAppCompatCallback.onSupportActionModeFinished(mActionMode);
+            }
+            mActionMode = null;
+        }
+    }
+
+    private final class PanelMenuPresenterCallback implements MenuPresenter.Callback {
+        PanelMenuPresenterCallback() {
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+            final Menu parentMenu = menu.getRootMenu();
+            final boolean isSubMenu = parentMenu != menu;
+            final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
+            if (panel != null) {
+                if (isSubMenu) {
+                    callOnPanelClosed(panel.featureId, panel, parentMenu);
+                    closePanel(panel, true);
+                } else {
+                    // Close the panel and only do the callback if the menu is being
+                    // closed completely, not if opening a sub menu
+                    closePanel(panel, allMenusAreClosing);
+                }
+            }
+        }
+
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            if (subMenu == null && mHasActionBar) {
+                Window.Callback cb = getWindowCallback();
+                if (cb != null && !mIsDestroyed) {
+                    cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu);
+                }
+            }
+            return true;
+        }
+    }
+
+    private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
+        ActionMenuPresenterCallback() {
+        }
+
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            Window.Callback cb = getWindowCallback();
+            if (cb != null) {
+                cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu);
+            }
+            return true;
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+            checkCloseActionMenu(menu);
+        }
+    }
+
+    protected static final class PanelFeatureState {
+
+        /** Feature ID for this panel. */
+        int featureId;
+
+        int background;
+
+        int gravity;
+
+        int x;
+
+        int y;
+
+        int windowAnimations;
+
+        /** Dynamic state of the panel. */
+        ViewGroup decorView;
+
+        /** The panel that we are actually showing. */
+        View shownPanelView;
+
+        /** The panel that was returned by onCreatePanelView(). */
+        View createdPanelView;
+
+        /** Use {@link #setMenu} to set this. */
+        MenuBuilder menu;
+
+        ListMenuPresenter listMenuPresenter;
+
+        Context listPresenterContext;
+
+        /**
+         * Whether the panel has been prepared (see
+         * {@link #preparePanel}).
+         */
+        boolean isPrepared;
+
+        /**
+         * Whether an item's action has been performed. This happens in obvious
+         * scenarios (user clicks on menu item), but can also happen with
+         * chording menu+(shortcut key).
+         */
+        boolean isHandled;
+
+        boolean isOpen;
+
+        public boolean qwertyMode;
+
+        boolean refreshDecorView;
+
+        boolean refreshMenuContent;
+
+        boolean wasLastOpen;
+
+        /**
+         * Contains the state of the menu when told to freeze.
+         */
+        Bundle frozenMenuState;
+
+        /**
+         * Contains the state of associated action views when told to freeze.
+         * These are saved across invalidations.
+         */
+        Bundle frozenActionViewState;
+
+        PanelFeatureState(int featureId) {
+            this.featureId = featureId;
+
+            refreshDecorView = false;
+        }
+
+        public boolean hasPanelItems() {
+            if (shownPanelView == null) return false;
+            if (createdPanelView != null) return true;
+
+            return listMenuPresenter.getAdapter().getCount() > 0;
+        }
+
+        /**
+         * Unregister and free attached MenuPresenters. They will be recreated as needed.
+         */
+        public void clearMenuPresenters() {
+            if (menu != null) {
+                menu.removeMenuPresenter(listMenuPresenter);
+            }
+            listMenuPresenter = null;
+        }
+
+        void setStyle(Context context) {
+            final TypedValue outValue = new TypedValue();
+            final Resources.Theme widgetTheme = context.getResources().newTheme();
+            widgetTheme.setTo(context.getTheme());
+
+            // First apply the actionBarPopupTheme
+            widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true);
+            if (outValue.resourceId != 0) {
+                widgetTheme.applyStyle(outValue.resourceId, true);
+            }
+
+            // Now apply the panelMenuListTheme
+            widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
+            if (outValue.resourceId != 0) {
+                widgetTheme.applyStyle(outValue.resourceId, true);
+            } else {
+                widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true);
+            }
+
+            context = new ContextThemeWrapper(context, 0);
+            context.getTheme().setTo(widgetTheme);
+
+            listPresenterContext = context;
+
+            TypedArray a = context.obtainStyledAttributes(R.styleable.AppCompatTheme);
+            background = a.getResourceId(
+                    R.styleable.AppCompatTheme_panelBackground, 0);
+            windowAnimations = a.getResourceId(
+                    R.styleable.AppCompatTheme_android_windowAnimationStyle, 0);
+            a.recycle();
+        }
+
+        void setMenu(MenuBuilder menu) {
+            if (menu == this.menu) return;
+
+            if (this.menu != null) {
+                this.menu.removeMenuPresenter(listMenuPresenter);
+            }
+            this.menu = menu;
+            if (menu != null) {
+                if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter);
+            }
+        }
+
+        MenuView getListMenuView(MenuPresenter.Callback cb) {
+            if (menu == null) return null;
+
+            if (listMenuPresenter == null) {
+                listMenuPresenter = new ListMenuPresenter(listPresenterContext,
+                        R.layout.abc_list_menu_item_layout);
+                listMenuPresenter.setCallback(cb);
+                menu.addMenuPresenter(listMenuPresenter);
+            }
+
+            MenuView result = listMenuPresenter.getMenuView(decorView);
+
+            return result;
+        }
+
+        Parcelable onSaveInstanceState() {
+            SavedState savedState = new SavedState();
+            savedState.featureId = featureId;
+            savedState.isOpen = isOpen;
+
+            if (menu != null) {
+                savedState.menuState = new Bundle();
+                menu.savePresenterStates(savedState.menuState);
+            }
+
+            return savedState;
+        }
+
+        void onRestoreInstanceState(Parcelable state) {
+            SavedState savedState = (SavedState) state;
+            featureId = savedState.featureId;
+            wasLastOpen = savedState.isOpen;
+            frozenMenuState = savedState.menuState;
+
+            shownPanelView = null;
+            decorView = null;
+        }
+
+        void applyFrozenState() {
+            if (menu != null && frozenMenuState != null) {
+                menu.restorePresenterStates(frozenMenuState);
+                frozenMenuState = null;
+            }
+        }
+
+        private static class SavedState implements Parcelable {
+            int featureId;
+            boolean isOpen;
+            Bundle menuState;
+
+            SavedState() {
+            }
+
+            @Override
+            public int describeContents() {
+                return 0;
+            }
+
+            @Override
+            public void writeToParcel(Parcel dest, int flags) {
+                dest.writeInt(featureId);
+                dest.writeInt(isOpen ? 1 : 0);
+
+                if (isOpen) {
+                    dest.writeBundle(menuState);
+                }
+            }
+
+            static SavedState readFromParcel(Parcel source, ClassLoader loader) {
+                SavedState savedState = new SavedState();
+                savedState.featureId = source.readInt();
+                savedState.isOpen = source.readInt() == 1;
+
+                if (savedState.isOpen) {
+                    savedState.menuState = source.readBundle(loader);
+                }
+
+                return savedState;
+            }
+
+            public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
+                @Override
+                public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+                    return readFromParcel(in, loader);
+                }
+
+                @Override
+                public SavedState createFromParcel(Parcel in) {
+                    return readFromParcel(in, null);
+                }
+
+                @Override
+                public SavedState[] newArray(int size) {
+                    return new SavedState[size];
+                }
+            };
+        }
+    }
+
+    private class ListMenuDecorView extends ContentFrameLayout {
+        public ListMenuDecorView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            return AppCompatDelegateImplBase.this.dispatchKeyEvent(event)
+                    || super.dispatchKeyEvent(event);
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent event) {
+            int action = event.getAction();
+            if (action == MotionEvent.ACTION_DOWN) {
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+                if (isOutOfBounds(x, y)) {
+                    closePanel(Window.FEATURE_OPTIONS_PANEL);
+                    return true;
+                }
+            }
+            return super.onInterceptTouchEvent(event);
+        }
+
+        @Override
+        public void setBackgroundResource(int resid) {
+            setBackgroundDrawable(AppCompatResources.getDrawable(getContext(), resid));
+        }
+
+        private boolean isOutOfBounds(int x, int y) {
+            return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5);
+        }
+    }
+
+
+    class AppCompatWindowCallbackV14 extends WindowCallbackWrapper {
+        AppCompatWindowCallbackV14(Window.Callback callback) {
             super(callback);
         }
 
@@ -369,14 +2557,154 @@
         @Override
         public boolean onMenuOpened(int featureId, Menu menu) {
             super.onMenuOpened(featureId, menu);
-            AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu);
+            AppCompatDelegateImplBase.this.onMenuOpened(featureId);
             return true;
         }
 
         @Override
         public void onPanelClosed(int featureId, Menu menu) {
             super.onPanelClosed(featureId, menu);
-            AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu);
+            AppCompatDelegateImplBase.this.onPanelClosed(featureId);
+        }
+
+        @Override
+        public android.view.ActionMode onWindowStartingActionMode(
+                android.view.ActionMode.Callback callback) {
+            // We wrap in a support action mode on v14+ if enabled
+            if (isHandleNativeActionModesEnabled()) {
+                return startAsSupportActionMode(callback);
+            }
+            // Else, let the call fall through to the wrapped callback
+            return super.onWindowStartingActionMode(callback);
+        }
+
+        /**
+         * Wrap the framework {@link android.view.ActionMode.Callback} in a support action mode and
+         * let AppCompat display it.
+         */
+        final android.view.ActionMode startAsSupportActionMode(
+                android.view.ActionMode.Callback callback) {
+            // Wrap the callback as a v7 ActionMode.Callback
+            final SupportActionModeWrapper.CallbackWrapper callbackWrapper =
+                    new SupportActionModeWrapper.CallbackWrapper(mContext, callback);
+
+            // Try and start a support action mode using the wrapped callback
+            final androidx.appcompat.view.ActionMode supportActionMode =
+                    startSupportActionMode(callbackWrapper);
+
+            if (supportActionMode != null) {
+                // If we received a support action mode, wrap and return it
+                return callbackWrapper.getActionModeWrapper(supportActionMode);
+            }
+            return null;
+        }
+    }
+
+    @VisibleForTesting
+    final class AutoNightModeManager {
+        private TwilightManager mTwilightManager;
+        private boolean mIsNight;
+
+        private BroadcastReceiver mAutoTimeChangeReceiver;
+        private IntentFilter mAutoTimeChangeReceiverFilter;
+
+        AutoNightModeManager(@NonNull TwilightManager twilightManager) {
+            mTwilightManager = twilightManager;
+            mIsNight = twilightManager.isNight();
+        }
+
+        @ApplyableNightMode
+        int getApplyableNightMode() {
+            mIsNight = mTwilightManager.isNight();
+            return mIsNight ? MODE_NIGHT_YES : MODE_NIGHT_NO;
+        }
+
+        void dispatchTimeChanged() {
+            final boolean isNight = mTwilightManager.isNight();
+            if (isNight != mIsNight) {
+                mIsNight = isNight;
+                applyDayNight();
+            }
+        }
+
+        void setup() {
+            cleanup();
+
+            // If we're set to AUTO, we register a receiver to be notified on time changes. The
+            // system only sends the tick out every minute, but that's enough fidelity for our use
+            // case
+            if (mAutoTimeChangeReceiver == null) {
+                mAutoTimeChangeReceiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (DEBUG) {
+                            Log.d("AutoTimeChangeReceiver", "onReceive | Intent: " + intent);
+                        }
+                        dispatchTimeChanged();
+                    }
+                };
+            }
+            if (mAutoTimeChangeReceiverFilter == null) {
+                mAutoTimeChangeReceiverFilter = new IntentFilter();
+                mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIME_CHANGED);
+                mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+                mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIME_TICK);
+            }
+            mContext.registerReceiver(mAutoTimeChangeReceiver, mAutoTimeChangeReceiverFilter);
+        }
+
+        void cleanup() {
+            if (mAutoTimeChangeReceiver != null) {
+                mContext.unregisterReceiver(mAutoTimeChangeReceiver);
+                mAutoTimeChangeReceiver = null;
+            }
+        }
+    }
+
+    @Override
+    public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
+        return new ActionBarDrawableToggleImpl();
+    }
+
+    private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate {
+        ActionBarDrawableToggleImpl() {
+        }
+
+        @Override
+        public Drawable getThemeUpIndicator() {
+            final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
+                    getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator });
+            final Drawable result = a.getDrawable(0);
+            a.recycle();
+            return result;
+        }
+
+        @Override
+        public Context getActionBarThemedContext() {
+            return AppCompatDelegateImplBase.this.getActionBarThemedContext();
+        }
+
+        @Override
+        public boolean isNavigationVisible() {
+            final ActionBar ab = getSupportActionBar();
+            return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+        }
+
+        @Override
+        public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.setHomeAsUpIndicator(upDrawable);
+                ab.setHomeActionContentDescription(contentDescRes);
+            }
+        }
+
+        @Override
+        public void setActionBarDescription(int contentDescRes) {
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.setHomeActionContentDescription(contentDescRes);
+            }
         }
     }
 }
diff --git a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV14.java b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV14.java
deleted file mode 100644
index 67412f4..0000000
--- a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV14.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package androidx.appcompat.app;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.ActionMode;
-import android.view.View;
-import android.view.Window;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.view.SupportActionModeWrapper;
-
-@RequiresApi(14)
-class AppCompatDelegateImplV14 extends AppCompatDelegateImplV9 {
-
-    private static final String KEY_LOCAL_NIGHT_MODE = "appcompat:local_night_mode";
-
-    @NightMode
-    private int mLocalNightMode = MODE_NIGHT_UNSPECIFIED;
-    private boolean mApplyDayNightCalled;
-
-    private boolean mHandleNativeActionModes = true; // defaults to true
-
-    private AutoNightModeManager mAutoNightModeManager;
-
-    AppCompatDelegateImplV14(Context context, Window window, AppCompatCallback callback) {
-        super(context, window, callback);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        if (savedInstanceState != null && mLocalNightMode == MODE_NIGHT_UNSPECIFIED) {
-            // If we have a icicle and we haven't had a local night mode set yet, try and read
-            // it from the icicle
-            mLocalNightMode = savedInstanceState.getInt(KEY_LOCAL_NIGHT_MODE,
-                    MODE_NIGHT_UNSPECIFIED);
-        }
-    }
-
-    @Override
-    public boolean hasWindowFeature(int featureId) {
-        return super.hasWindowFeature(featureId) || mWindow.hasFeature(featureId);
-    }
-
-    @Override
-    View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
-        // On Honeycomb+, Activity's private inflater factory will handle calling its
-        // onCreateView(...)
-        return null;
-    }
-
-    @Override
-    Window.Callback wrapWindowCallback(Window.Callback callback) {
-        // Override the window callback so that we can intercept onWindowStartingActionMode()
-        // calls
-        return new AppCompatWindowCallbackV14(callback);
-    }
-
-    @Override
-    public void setHandleNativeActionModesEnabled(boolean enabled) {
-        mHandleNativeActionModes = enabled;
-    }
-
-    @Override
-    public boolean isHandleNativeActionModesEnabled() {
-        return mHandleNativeActionModes;
-    }
-
-    @Override
-    public boolean applyDayNight() {
-        boolean applied = false;
-
-        @NightMode final int nightMode = getNightMode();
-        @ApplyableNightMode final int modeToApply = mapNightMode(nightMode);
-        if (modeToApply != MODE_NIGHT_FOLLOW_SYSTEM) {
-            applied = updateForNightMode(modeToApply);
-        }
-
-        if (nightMode == MODE_NIGHT_AUTO) {
-            // If we're already been started, we may need to setup auto mode again
-            ensureAutoNightModeManager();
-            mAutoNightModeManager.setup();
-        }
-
-        mApplyDayNightCalled = true;
-        return applied;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        // This will apply day/night if the time has changed, it will also call through to
-        // setupAutoNightModeIfNeeded()
-        applyDayNight();
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-
-        // Make sure we clean up any receivers setup for AUTO mode
-        if (mAutoNightModeManager != null) {
-            mAutoNightModeManager.cleanup();
-        }
-    }
-
-    @Override
-    public void setLocalNightMode(@NightMode final int mode) {
-        switch (mode) {
-            case MODE_NIGHT_AUTO:
-            case MODE_NIGHT_NO:
-            case MODE_NIGHT_YES:
-            case MODE_NIGHT_FOLLOW_SYSTEM:
-                if (mLocalNightMode != mode) {
-                    mLocalNightMode = mode;
-                    if (mApplyDayNightCalled) {
-                        // If we've already applied day night, re-apply since we won't be
-                        // called again
-                        applyDayNight();
-                    }
-                }
-                break;
-            default:
-                Log.i(TAG, "setLocalNightMode() called with an unknown mode");
-                break;
-        }
-    }
-
-    @ApplyableNightMode
-    int mapNightMode(@NightMode final int mode) {
-        switch (mode) {
-            case MODE_NIGHT_AUTO:
-                ensureAutoNightModeManager();
-                return mAutoNightModeManager.getApplyableNightMode();
-            case MODE_NIGHT_UNSPECIFIED:
-                // If we don't have a mode specified, just let the system handle it
-                return MODE_NIGHT_FOLLOW_SYSTEM;
-            default:
-                return mode;
-        }
-    }
-
-    @NightMode
-    private int getNightMode() {
-        return mLocalNightMode != MODE_NIGHT_UNSPECIFIED ? mLocalNightMode : getDefaultNightMode();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-
-        if (mLocalNightMode != MODE_NIGHT_UNSPECIFIED) {
-            // If we have a local night mode set, save it
-            outState.putInt(KEY_LOCAL_NIGHT_MODE, mLocalNightMode);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-
-        // Make sure we clean up any receivers setup for AUTO mode
-        if (mAutoNightModeManager != null) {
-            mAutoNightModeManager.cleanup();
-        }
-    }
-
-    /**
-     * Updates the {@link Resources} configuration {@code uiMode} with the
-     * chosen {@code UI_MODE_NIGHT} value.
-     */
-    private boolean updateForNightMode(@ApplyableNightMode final int mode) {
-        final Resources res = mContext.getResources();
-        final Configuration conf = res.getConfiguration();
-        final int currentNightMode = conf.uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-        final int newNightMode = (mode == MODE_NIGHT_YES)
-                ? Configuration.UI_MODE_NIGHT_YES
-                : Configuration.UI_MODE_NIGHT_NO;
-
-        if (currentNightMode != newNightMode) {
-            if (shouldRecreateOnNightModeChange()) {
-                if (DEBUG) {
-                    Log.d(TAG, "applyNightMode() | Night mode changed, recreating Activity");
-                }
-                // If we've already been created, we need to recreate the Activity for the
-                // mode to be applied
-                final Activity activity = (Activity) mContext;
-                activity.recreate();
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "applyNightMode() | Night mode changed, updating configuration");
-                }
-                final Configuration config = new Configuration(conf);
-                final DisplayMetrics metrics = res.getDisplayMetrics();
-
-                // Update the UI Mode to reflect the new night mode
-                config.uiMode = newNightMode | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
-                res.updateConfiguration(config, metrics);
-
-                // We may need to flush the Resources' drawable cache due to framework bugs.
-                if (!(Build.VERSION.SDK_INT >= 26)) {
-                    ResourcesFlusher.flush(res);
-                }
-            }
-            return true;
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "applyNightMode() | Skipping. Night mode has not changed: " + mode);
-            }
-        }
-        return false;
-    }
-
-    private void ensureAutoNightModeManager() {
-        if (mAutoNightModeManager == null) {
-            mAutoNightModeManager = new AutoNightModeManager(TwilightManager.getInstance(mContext));
-        }
-    }
-
-    @VisibleForTesting
-    final AutoNightModeManager getAutoNightModeManager() {
-        ensureAutoNightModeManager();
-        return mAutoNightModeManager;
-    }
-
-    private boolean shouldRecreateOnNightModeChange() {
-        if (mApplyDayNightCalled && mContext instanceof Activity) {
-            // If we've already applyDayNight() (via setTheme), we need to check if the
-            // Activity has configChanges set to handle uiMode changes
-            final PackageManager pm = mContext.getPackageManager();
-            try {
-                final ActivityInfo info = pm.getActivityInfo(
-                        new ComponentName(mContext, mContext.getClass()), 0);
-                // We should return true (to recreate) if configChanges does not want to
-                // handle uiMode
-                return (info.configChanges & ActivityInfo.CONFIG_UI_MODE) == 0;
-            } catch (PackageManager.NameNotFoundException e) {
-                // This shouldn't happen but let's not crash because of it, we'll just log and
-                // return true (since most apps will do that anyway)
-                Log.d(TAG, "Exception while getting ActivityInfo", e);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    class AppCompatWindowCallbackV14 extends AppCompatWindowCallbackBase {
-        AppCompatWindowCallbackV14(Window.Callback callback) {
-            super(callback);
-        }
-
-        @Override
-        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
-            // We wrap in a support action mode on v14+ if enabled
-            if (isHandleNativeActionModesEnabled()) {
-                return startAsSupportActionMode(callback);
-            }
-            // Else, let the call fall through to the wrapped callback
-            return super.onWindowStartingActionMode(callback);
-        }
-
-        /**
-         * Wrap the framework {@link ActionMode.Callback} in a support action mode and
-         * let AppCompat display it.
-         */
-        final ActionMode startAsSupportActionMode(ActionMode.Callback callback) {
-            // Wrap the callback as a v7 ActionMode.Callback
-            final SupportActionModeWrapper.CallbackWrapper callbackWrapper
-                    = new SupportActionModeWrapper.CallbackWrapper(mContext, callback);
-
-            // Try and start a support action mode using the wrapped callback
-            final androidx.appcompat.view.ActionMode supportActionMode
-                    = startSupportActionMode(callbackWrapper);
-
-            if (supportActionMode != null) {
-                // If we received a support action mode, wrap and return it
-                return callbackWrapper.getActionModeWrapper(supportActionMode);
-            }
-            return null;
-        }
-    }
-
-    @VisibleForTesting
-    final class AutoNightModeManager {
-        private TwilightManager mTwilightManager;
-        private boolean mIsNight;
-
-        private BroadcastReceiver mAutoTimeChangeReceiver;
-        private IntentFilter mAutoTimeChangeReceiverFilter;
-
-        AutoNightModeManager(@NonNull TwilightManager twilightManager) {
-            mTwilightManager = twilightManager;
-            mIsNight = twilightManager.isNight();
-        }
-
-        @ApplyableNightMode
-        final int getApplyableNightMode() {
-            mIsNight = mTwilightManager.isNight();
-            return mIsNight ? MODE_NIGHT_YES : MODE_NIGHT_NO;
-        }
-
-        final void dispatchTimeChanged() {
-            final boolean isNight = mTwilightManager.isNight();
-            if (isNight != mIsNight) {
-                mIsNight = isNight;
-                applyDayNight();
-            }
-        }
-
-        final void setup() {
-            cleanup();
-
-            // If we're set to AUTO, we register a receiver to be notified on time changes. The
-            // system only sends the tick out every minute, but that's enough fidelity for our use
-            // case
-            if (mAutoTimeChangeReceiver == null) {
-                mAutoTimeChangeReceiver = new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        if (DEBUG) {
-                            Log.d("AutoTimeChangeReceiver", "onReceive | Intent: " + intent);
-                        }
-                        dispatchTimeChanged();
-                    }
-                };
-            }
-            if (mAutoTimeChangeReceiverFilter == null) {
-                mAutoTimeChangeReceiverFilter = new IntentFilter();
-                mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIME_CHANGED);
-                mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-                mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIME_TICK);
-            }
-            mContext.registerReceiver(mAutoTimeChangeReceiver, mAutoTimeChangeReceiverFilter);
-        }
-
-        final void cleanup() {
-            if (mAutoTimeChangeReceiver != null) {
-                mContext.unregisterReceiver(mAutoTimeChangeReceiver);
-                mAutoTimeChangeReceiver = null;
-            }
-        }
-    }
-}
diff --git a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV23.java b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV23.java
index 0ed7409..fd4c781 100644
--- a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV23.java
+++ b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV23.java
@@ -24,7 +24,7 @@
 import androidx.annotation.RequiresApi;
 
 @RequiresApi(23)
-class AppCompatDelegateImplV23 extends AppCompatDelegateImplV14 {
+class AppCompatDelegateImplV23 extends AppCompatDelegateImplBase {
 
     private final UiModeManager mUiModeManager;
 
diff --git a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV9.java b/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV9.java
deleted file mode 100644
index a0d79ab..0000000
--- a/v7/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImplV9.java
+++ /dev/null
@@ -1,2163 +0,0 @@
-/*
- * Copyright (C) 2013 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 androidx.appcompat.app;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.Window.FEATURE_OPTIONS_PANEL;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.AndroidRuntimeException;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
-import android.widget.PopupWindow;
-import android.widget.TextView;
-
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.appcompat.R;
-import androidx.appcompat.content.res.AppCompatResources;
-import androidx.appcompat.view.ActionMode;
-import androidx.appcompat.view.ContextThemeWrapper;
-import androidx.appcompat.view.StandaloneActionMode;
-import androidx.appcompat.view.menu.ListMenuPresenter;
-import androidx.appcompat.view.menu.MenuBuilder;
-import androidx.appcompat.view.menu.MenuPresenter;
-import androidx.appcompat.view.menu.MenuView;
-import androidx.appcompat.widget.ActionBarContextView;
-import androidx.appcompat.widget.AppCompatDrawableManager;
-import androidx.appcompat.widget.ContentFrameLayout;
-import androidx.appcompat.widget.DecorContentParent;
-import androidx.appcompat.widget.FitWindowsViewGroup;
-import androidx.appcompat.widget.Toolbar;
-import androidx.appcompat.widget.VectorEnabledTintResources;
-import androidx.appcompat.widget.ViewStubCompat;
-import androidx.appcompat.widget.ViewUtils;
-import androidx.core.app.NavUtils;
-import androidx.core.view.LayoutInflaterCompat;
-import androidx.core.view.OnApplyWindowInsetsListener;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.ViewPropertyAnimatorCompat;
-import androidx.core.view.ViewPropertyAnimatorListenerAdapter;
-import androidx.core.view.WindowCompat;
-import androidx.core.view.WindowInsetsCompat;
-import androidx.core.widget.PopupWindowCompat;
-
-import org.xmlpull.v1.XmlPullParser;
-
-@RequiresApi(14)
-class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
-        implements MenuBuilder.Callback, LayoutInflater.Factory2 {
-
-    private static final boolean IS_PRE_LOLLIPOP = Build.VERSION.SDK_INT < 21;
-
-    private DecorContentParent mDecorContentParent;
-    private ActionMenuPresenterCallback mActionMenuPresenterCallback;
-    private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
-
-    ActionMode mActionMode;
-    ActionBarContextView mActionModeView;
-    PopupWindow mActionModePopup;
-    Runnable mShowActionModePopup;
-    ViewPropertyAnimatorCompat mFadeAnim = null;
-
-    // true if we have installed a window sub-decor layout.
-    private boolean mSubDecorInstalled;
-    private ViewGroup mSubDecor;
-
-    private TextView mTitleView;
-    private View mStatusGuard;
-
-    // Used to keep track of Progress Bar Window features
-    private boolean mFeatureProgress, mFeatureIndeterminateProgress;
-
-    // Used for emulating PanelFeatureState
-    private boolean mClosingActionMenu;
-    private PanelFeatureState[] mPanels;
-    private PanelFeatureState mPreparedPanel;
-
-    private boolean mLongPressBackDown;
-
-    boolean mInvalidatePanelMenuPosted;
-    int mInvalidatePanelMenuFeatures;
-    private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) {
-                doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL);
-            }
-            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_SUPPORT_ACTION_BAR) != 0) {
-                doInvalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
-            }
-            mInvalidatePanelMenuPosted = false;
-            mInvalidatePanelMenuFeatures = 0;
-        }
-    };
-
-    private boolean mEnableDefaultActionBarUp;
-
-    private Rect mTempRect1;
-    private Rect mTempRect2;
-
-    private AppCompatViewInflater mAppCompatViewInflater;
-
-    AppCompatDelegateImplV9(Context context, Window window, AppCompatCallback callback) {
-        super(context, window, callback);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        if (mOriginalWindowCallback instanceof Activity) {
-            if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) {
-                // Peek at the Action Bar and update it if it already exists
-                ActionBar ab = peekSupportActionBar();
-                if (ab == null) {
-                    mEnableDefaultActionBarUp = true;
-                } else {
-                    ab.setDefaultDisplayHomeAsUpEnabled(true);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onPostCreate(Bundle savedInstanceState) {
-        // Make sure that the sub decor is installed
-        ensureSubDecor();
-    }
-
-    @Override
-    public void initWindowDecorActionBar() {
-        ensureSubDecor();
-
-        if (!mHasActionBar || mActionBar != null) {
-            return;
-        }
-
-        if (mOriginalWindowCallback instanceof Activity) {
-            mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback,
-                    mOverlayActionBar);
-        } else if (mOriginalWindowCallback instanceof Dialog) {
-            mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback);
-        }
-        if (mActionBar != null) {
-            mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
-        }
-    }
-
-    @Override
-    public void setSupportActionBar(Toolbar toolbar) {
-        if (!(mOriginalWindowCallback instanceof Activity)) {
-            // Only Activities support custom Action Bars
-            return;
-        }
-
-        final ActionBar ab = getSupportActionBar();
-        if (ab instanceof WindowDecorActionBar) {
-            throw new IllegalStateException("This Activity already has an action bar supplied " +
-                    "by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " +
-                    "windowActionBar to false in your theme to use a Toolbar instead.");
-        }
-
-        // If we reach here then we're setting a new action bar
-        // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
-        mMenuInflater = null;
-
-        // If we have an action bar currently, destroy it
-        if (ab != null) {
-            ab.onDestroy();
-        }
-
-        if (toolbar != null) {
-            final ToolbarActionBar tbab = new ToolbarActionBar(toolbar,
-                    ((Activity) mOriginalWindowCallback).getTitle(), mAppCompatWindowCallback);
-            mActionBar = tbab;
-            mWindow.setCallback(tbab.getWrappedWindowCallback());
-        } else {
-            mActionBar = null;
-            // Re-set the original window callback since we may have already set a Toolbar wrapper
-            mWindow.setCallback(mAppCompatWindowCallback);
-        }
-
-        invalidateOptionsMenu();
-    }
-
-    @SuppressWarnings("TypeParameterUnusedInFormals")
-    @Nullable
-    @Override
-    public <T extends View> T findViewById(@IdRes int id) {
-        ensureSubDecor();
-        return (T) mWindow.findViewById(id);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        // If this is called before sub-decor is installed, ActionBar will not
-        // be properly initialized.
-        if (mHasActionBar && mSubDecorInstalled) {
-            // Note: The action bar will need to access
-            // view changes from superclass.
-            ActionBar ab = getSupportActionBar();
-            if (ab != null) {
-                ab.onConfigurationChanged(newConfig);
-            }
-        }
-
-        // Make sure that the DrawableManager knows about the new config
-        AppCompatDrawableManager.get().onConfigurationChanged(mContext);
-
-        // Re-apply Day/Night to the new configuration
-        applyDayNight();
-    }
-
-    @Override
-    public void onStop() {
-        ActionBar ab = getSupportActionBar();
-        if (ab != null) {
-            ab.setShowHideAnimationEnabled(false);
-        }
-    }
-
-    @Override
-    public void onPostResume() {
-        ActionBar ab = getSupportActionBar();
-        if (ab != null) {
-            ab.setShowHideAnimationEnabled(true);
-        }
-    }
-
-    @Override
-    public void setContentView(View v) {
-        ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
-        contentParent.removeAllViews();
-        contentParent.addView(v);
-        mOriginalWindowCallback.onContentChanged();
-    }
-
-    @Override
-    public void setContentView(int resId) {
-        ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
-        contentParent.removeAllViews();
-        LayoutInflater.from(mContext).inflate(resId, contentParent);
-        mOriginalWindowCallback.onContentChanged();
-    }
-
-    @Override
-    public void setContentView(View v, ViewGroup.LayoutParams lp) {
-        ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
-        contentParent.removeAllViews();
-        contentParent.addView(v, lp);
-        mOriginalWindowCallback.onContentChanged();
-    }
-
-    @Override
-    public void addContentView(View v, ViewGroup.LayoutParams lp) {
-        ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
-        contentParent.addView(v, lp);
-        mOriginalWindowCallback.onContentChanged();
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mInvalidatePanelMenuPosted) {
-            mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
-        }
-
-        super.onDestroy();
-
-        if (mActionBar != null) {
-            mActionBar.onDestroy();
-        }
-    }
-
-    private void ensureSubDecor() {
-        if (!mSubDecorInstalled) {
-            mSubDecor = createSubDecor();
-
-            // If a title was set before we installed the decor, propagate it now
-            CharSequence title = getTitle();
-            if (!TextUtils.isEmpty(title)) {
-                onTitleChanged(title);
-            }
-
-            applyFixedSizeWindow();
-
-            onSubDecorInstalled(mSubDecor);
-
-            mSubDecorInstalled = true;
-
-            // Invalidate if the panel menu hasn't been created before this.
-            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
-            // being called in the middle of onCreate or similar.
-            // A pending invalidation will typically be resolved before the posted message
-            // would run normally in order to satisfy instance state restoration.
-            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
-            if (!isDestroyed() && (st == null || st.menu == null)) {
-                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
-            }
-        }
-    }
-
-    private ViewGroup createSubDecor() {
-        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
-
-        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
-            a.recycle();
-            throw new IllegalStateException(
-                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
-        }
-
-        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
-            requestWindowFeature(Window.FEATURE_NO_TITLE);
-        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
-            // Don't allow an action bar if there is no title.
-            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
-        }
-        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
-            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
-        }
-        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
-            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
-        }
-        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
-        a.recycle();
-
-        // Now let's make sure that the Window has installed its decor by retrieving it
-        mWindow.getDecorView();
-
-        final LayoutInflater inflater = LayoutInflater.from(mContext);
-        ViewGroup subDecor = null;
-
-
-        if (!mWindowNoTitle) {
-            if (mIsFloating) {
-                // If we're floating, inflate the dialog title decor
-                subDecor = (ViewGroup) inflater.inflate(
-                        R.layout.abc_dialog_title_material, null);
-
-                // Floating windows can never have an action bar, reset the flags
-                mHasActionBar = mOverlayActionBar = false;
-            } else if (mHasActionBar) {
-                /**
-                 * This needs some explanation. As we can not use the android:theme attribute
-                 * pre-L, we emulate it by manually creating a LayoutInflater using a
-                 * ContextThemeWrapper pointing to actionBarTheme.
-                 */
-                TypedValue outValue = new TypedValue();
-                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
-
-                Context themedContext;
-                if (outValue.resourceId != 0) {
-                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
-                } else {
-                    themedContext = mContext;
-                }
-
-                // Now inflate the view using the themed context and set it as the content view
-                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
-                        .inflate(R.layout.abc_screen_toolbar, null);
-
-                mDecorContentParent = (DecorContentParent) subDecor
-                        .findViewById(R.id.decor_content_parent);
-                mDecorContentParent.setWindowCallback(getWindowCallback());
-
-                /**
-                 * Propagate features to DecorContentParent
-                 */
-                if (mOverlayActionBar) {
-                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
-                }
-                if (mFeatureProgress) {
-                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
-                }
-                if (mFeatureIndeterminateProgress) {
-                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-                }
-            }
-        } else {
-            if (mOverlayActionMode) {
-                subDecor = (ViewGroup) inflater.inflate(
-                        R.layout.abc_screen_simple_overlay_action_mode, null);
-            } else {
-                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
-            }
-
-            if (Build.VERSION.SDK_INT >= 21) {
-                // If we're running on L or above, we can rely on ViewCompat's
-                // setOnApplyWindowInsetsListener
-                ViewCompat.setOnApplyWindowInsetsListener(subDecor,
-                        new OnApplyWindowInsetsListener() {
-                            @Override
-                            public WindowInsetsCompat onApplyWindowInsets(View v,
-                                    WindowInsetsCompat insets) {
-                                final int top = insets.getSystemWindowInsetTop();
-                                final int newTop = updateStatusGuard(top);
-
-                                if (top != newTop) {
-                                    insets = insets.replaceSystemWindowInsets(
-                                            insets.getSystemWindowInsetLeft(),
-                                            newTop,
-                                            insets.getSystemWindowInsetRight(),
-                                            insets.getSystemWindowInsetBottom());
-                                }
-
-                                // Now apply the insets on our view
-                                return ViewCompat.onApplyWindowInsets(v, insets);
-                            }
-                        });
-            } else {
-                // Else, we need to use our own FitWindowsViewGroup handling
-                ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
-                        new FitWindowsViewGroup.OnFitSystemWindowsListener() {
-                            @Override
-                            public void onFitSystemWindows(Rect insets) {
-                                insets.top = updateStatusGuard(insets.top);
-                            }
-                        });
-            }
-        }
-
-        if (subDecor == null) {
-            throw new IllegalArgumentException(
-                    "AppCompat does not support the current theme features: { "
-                            + "windowActionBar: " + mHasActionBar
-                            + ", windowActionBarOverlay: "+ mOverlayActionBar
-                            + ", android:windowIsFloating: " + mIsFloating
-                            + ", windowActionModeOverlay: " + mOverlayActionMode
-                            + ", windowNoTitle: " + mWindowNoTitle
-                            + " }");
-        }
-
-        if (mDecorContentParent == null) {
-            mTitleView = (TextView) subDecor.findViewById(R.id.title);
-        }
-
-        // Make the decor optionally fit system windows, like the window's decor
-        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
-
-        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
-                R.id.action_bar_activity_content);
-
-        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
-        if (windowContentView != null) {
-            // There might be Views already added to the Window's content view so we need to
-            // migrate them to our content view
-            while (windowContentView.getChildCount() > 0) {
-                final View child = windowContentView.getChildAt(0);
-                windowContentView.removeViewAt(0);
-                contentView.addView(child);
-            }
-
-            // Change our content FrameLayout to use the android.R.id.content id.
-            // Useful for fragments.
-            windowContentView.setId(View.NO_ID);
-            contentView.setId(android.R.id.content);
-
-            // The decorContent may have a foreground drawable set (windowContentOverlay).
-            // Remove this as we handle it ourselves
-            if (windowContentView instanceof FrameLayout) {
-                ((FrameLayout) windowContentView).setForeground(null);
-            }
-        }
-
-        // Now set the Window's content view with the decor
-        mWindow.setContentView(subDecor);
-
-        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
-            @Override
-            public void onAttachedFromWindow() {}
-
-            @Override
-            public void onDetachedFromWindow() {
-                dismissPopups();
-            }
-        });
-
-        return subDecor;
-    }
-
-    void onSubDecorInstalled(ViewGroup subDecor) {}
-
-    private void applyFixedSizeWindow() {
-        ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);
-
-        // This is a bit weird. In the framework, the window sizing attributes control
-        // the decor view's size, meaning that any padding is inset for the min/max widths below.
-        // We don't control measurement at that level, so we need to workaround it by making sure
-        // that the decor view's padding is taken into account.
-        final View windowDecor = mWindow.getDecorView();
-        cfl.setDecorPadding(windowDecor.getPaddingLeft(),
-                windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
-                windowDecor.getPaddingBottom());
-
-        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
-        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
-        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
-
-        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
-            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
-                    cfl.getFixedWidthMajor());
-        }
-        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
-            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
-                    cfl.getFixedWidthMinor());
-        }
-        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
-            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
-                    cfl.getFixedHeightMajor());
-        }
-        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
-            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
-                    cfl.getFixedHeightMinor());
-        }
-        a.recycle();
-
-        cfl.requestLayout();
-    }
-
-    @Override
-    public boolean requestWindowFeature(int featureId) {
-        featureId = sanitizeWindowFeatureId(featureId);
-
-        if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
-            return false; // Ignore. No title dominates.
-        }
-        if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
-            // Remove the action bar feature if we have no title. No title dominates.
-            mHasActionBar = false;
-        }
-
-        switch (featureId) {
-            case FEATURE_SUPPORT_ACTION_BAR:
-                throwFeatureRequestIfSubDecorInstalled();
-                mHasActionBar = true;
-                return true;
-            case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
-                throwFeatureRequestIfSubDecorInstalled();
-                mOverlayActionBar = true;
-                return true;
-            case FEATURE_ACTION_MODE_OVERLAY:
-                throwFeatureRequestIfSubDecorInstalled();
-                mOverlayActionMode = true;
-                return true;
-            case Window.FEATURE_PROGRESS:
-                throwFeatureRequestIfSubDecorInstalled();
-                mFeatureProgress = true;
-                return true;
-            case Window.FEATURE_INDETERMINATE_PROGRESS:
-                throwFeatureRequestIfSubDecorInstalled();
-                mFeatureIndeterminateProgress = true;
-                return true;
-            case Window.FEATURE_NO_TITLE:
-                throwFeatureRequestIfSubDecorInstalled();
-                mWindowNoTitle = true;
-                return true;
-        }
-
-        return mWindow.requestFeature(featureId);
-    }
-
-    @Override
-    public boolean hasWindowFeature(int featureId) {
-        featureId = sanitizeWindowFeatureId(featureId);
-        switch (featureId) {
-            case FEATURE_SUPPORT_ACTION_BAR:
-                return mHasActionBar;
-            case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
-                return mOverlayActionBar;
-            case FEATURE_ACTION_MODE_OVERLAY:
-                return mOverlayActionMode;
-            case Window.FEATURE_PROGRESS:
-                return mFeatureProgress;
-            case Window.FEATURE_INDETERMINATE_PROGRESS:
-                return mFeatureIndeterminateProgress;
-            case Window.FEATURE_NO_TITLE:
-                return mWindowNoTitle;
-        }
-        return false;
-    }
-
-    @Override
-    void onTitleChanged(CharSequence title) {
-        if (mDecorContentParent != null) {
-            mDecorContentParent.setWindowTitle(title);
-        } else if (peekSupportActionBar() != null) {
-            peekSupportActionBar().setWindowTitle(title);
-        } else if (mTitleView != null) {
-            mTitleView.setText(title);
-        }
-    }
-
-    @Override
-    void onPanelClosed(final int featureId, Menu menu) {
-        if (featureId == FEATURE_SUPPORT_ACTION_BAR) {
-            ActionBar ab = getSupportActionBar();
-            if (ab != null) {
-                ab.dispatchMenuVisibilityChanged(false);
-            }
-        } else if (featureId == FEATURE_OPTIONS_PANEL) {
-            // Make sure that the options panel is closed. This is mainly used when we're using a
-            // ToolbarActionBar
-            PanelFeatureState st = getPanelState(featureId, true);
-            if (st.isOpen) {
-                closePanel(st, false);
-            }
-        }
-    }
-
-    @Override
-    boolean onMenuOpened(final int featureId, Menu menu) {
-        if (featureId == FEATURE_SUPPORT_ACTION_BAR) {
-            ActionBar ab = getSupportActionBar();
-            if (ab != null) {
-                ab.dispatchMenuVisibilityChanged(true);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
-        final Window.Callback cb = getWindowCallback();
-        if (cb != null && !isDestroyed()) {
-            final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
-            if (panel != null) {
-                return cb.onMenuItemSelected(panel.featureId, item);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void onMenuModeChange(MenuBuilder menu) {
-        reopenMenu(menu, true);
-    }
-
-    @Override
-    public ActionMode startSupportActionMode(@NonNull final ActionMode.Callback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("ActionMode callback can not be null.");
-        }
-
-        if (mActionMode != null) {
-            mActionMode.finish();
-        }
-
-        final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV9(callback);
-
-        ActionBar ab = getSupportActionBar();
-        if (ab != null) {
-            mActionMode = ab.startActionMode(wrappedCallback);
-            if (mActionMode != null && mAppCompatCallback != null) {
-                mAppCompatCallback.onSupportActionModeStarted(mActionMode);
-            }
-        }
-
-        if (mActionMode == null) {
-            // If the action bar didn't provide an action mode, start the emulated window one
-            mActionMode = startSupportActionModeFromWindow(wrappedCallback);
-        }
-
-        return mActionMode;
-    }
-
-    @Override
-    public void invalidateOptionsMenu() {
-        final ActionBar ab = getSupportActionBar();
-        if (ab != null && ab.invalidateOptionsMenu()) return;
-
-        invalidatePanelMenu(FEATURE_OPTIONS_PANEL);
-    }
-
-    @Override
-    ActionMode startSupportActionModeFromWindow(@NonNull ActionMode.Callback callback) {
-        endOnGoingFadeAnimation();
-        if (mActionMode != null) {
-            mActionMode.finish();
-        }
-
-        if (!(callback instanceof ActionModeCallbackWrapperV9)) {
-            // If the callback hasn't been wrapped yet, wrap it
-            callback = new ActionModeCallbackWrapperV9(callback);
-        }
-
-        ActionMode mode = null;
-        if (mAppCompatCallback != null && !isDestroyed()) {
-            try {
-                mode = mAppCompatCallback.onWindowStartingSupportActionMode(callback);
-            } catch (AbstractMethodError ame) {
-                // Older apps might not implement this callback method.
-            }
-        }
-
-        if (mode != null) {
-            mActionMode = mode;
-        } else {
-            if (mActionModeView == null) {
-                if (mIsFloating) {
-                    // Use the action bar theme.
-                    final TypedValue outValue = new TypedValue();
-                    final Resources.Theme baseTheme = mContext.getTheme();
-                    baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
-
-                    final Context actionBarContext;
-                    if (outValue.resourceId != 0) {
-                        final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
-                        actionBarTheme.setTo(baseTheme);
-                        actionBarTheme.applyStyle(outValue.resourceId, true);
-
-                        actionBarContext = new ContextThemeWrapper(mContext, 0);
-                        actionBarContext.getTheme().setTo(actionBarTheme);
-                    } else {
-                        actionBarContext = mContext;
-                    }
-
-                    mActionModeView = new ActionBarContextView(actionBarContext);
-                    mActionModePopup = new PopupWindow(actionBarContext, null,
-                            R.attr.actionModePopupWindowStyle);
-                    PopupWindowCompat.setWindowLayoutType(mActionModePopup,
-                            WindowManager.LayoutParams.TYPE_APPLICATION);
-                    mActionModePopup.setContentView(mActionModeView);
-                    mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
-
-                    actionBarContext.getTheme().resolveAttribute(
-                            R.attr.actionBarSize, outValue, true);
-                    final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
-                            actionBarContext.getResources().getDisplayMetrics());
-                    mActionModeView.setContentHeight(height);
-                    mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
-                    mShowActionModePopup = new Runnable() {
-                        @Override
-                        public void run() {
-                            mActionModePopup.showAtLocation(
-                                    mActionModeView,
-                                    Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
-                            endOnGoingFadeAnimation();
-
-                            if (shouldAnimateActionModeView()) {
-                                mActionModeView.setAlpha(0f);
-                                mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
-                                mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
-                                    @Override
-                                    public void onAnimationStart(View view) {
-                                        mActionModeView.setVisibility(View.VISIBLE);
-                                    }
-
-                                    @Override
-                                    public void onAnimationEnd(View view) {
-                                        mActionModeView.setAlpha(1f);
-                                        mFadeAnim.setListener(null);
-                                        mFadeAnim = null;
-                                    }
-                                });
-                            } else {
-                                mActionModeView.setAlpha(1f);
-                                mActionModeView.setVisibility(View.VISIBLE);
-                            }
-                        }
-                    };
-                } else {
-                    ViewStubCompat stub = (ViewStubCompat) mSubDecor
-                            .findViewById(R.id.action_mode_bar_stub);
-                    if (stub != null) {
-                        // Set the layout inflater so that it is inflated with the action bar's context
-                        stub.setLayoutInflater(LayoutInflater.from(getActionBarThemedContext()));
-                        mActionModeView = (ActionBarContextView) stub.inflate();
-                    }
-                }
-            }
-
-            if (mActionModeView != null) {
-                endOnGoingFadeAnimation();
-                mActionModeView.killMode();
-                mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView,
-                        callback, mActionModePopup == null);
-                if (callback.onCreateActionMode(mode, mode.getMenu())) {
-                    mode.invalidate();
-                    mActionModeView.initForMode(mode);
-                    mActionMode = mode;
-
-                    if (shouldAnimateActionModeView()) {
-                        mActionModeView.setAlpha(0f);
-                        mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
-                        mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationStart(View view) {
-                                mActionModeView.setVisibility(View.VISIBLE);
-                                mActionModeView.sendAccessibilityEvent(
-                                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-                                if (mActionModeView.getParent() instanceof View) {
-                                    ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
-                                }
-                            }
-
-                            @Override
-                            public void onAnimationEnd(View view) {
-                                mActionModeView.setAlpha(1f);
-                                mFadeAnim.setListener(null);
-                                mFadeAnim = null;
-                            }
-                        });
-                    } else {
-                        mActionModeView.setAlpha(1f);
-                        mActionModeView.setVisibility(View.VISIBLE);
-                        mActionModeView.sendAccessibilityEvent(
-                                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-                        if (mActionModeView.getParent() instanceof View) {
-                            ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
-                        }
-                    }
-
-                    if (mActionModePopup != null) {
-                        mWindow.getDecorView().post(mShowActionModePopup);
-                    }
-                } else {
-                    mActionMode = null;
-                }
-            }
-        }
-        if (mActionMode != null && mAppCompatCallback != null) {
-            mAppCompatCallback.onSupportActionModeStarted(mActionMode);
-        }
-        return mActionMode;
-    }
-
-    final boolean shouldAnimateActionModeView() {
-        // We only to animate the action mode in if the sub decor has already been laid out.
-        // If it hasn't been laid out, it hasn't been drawn to screen yet.
-        return mSubDecorInstalled && mSubDecor != null && ViewCompat.isLaidOut(mSubDecor);
-    }
-
-    void endOnGoingFadeAnimation() {
-        if (mFadeAnim != null) {
-            mFadeAnim.cancel();
-        }
-    }
-
-    boolean onBackPressed() {
-        // Back cancels action modes first.
-        if (mActionMode != null) {
-            mActionMode.finish();
-            return true;
-        }
-
-        // Next collapse any expanded action views.
-        ActionBar ab = getSupportActionBar();
-        if (ab != null && ab.collapseActionView()) {
-            return true;
-        }
-
-        // Let the call through...
-        return false;
-    }
-
-    @Override
-    boolean onKeyShortcut(int keyCode, KeyEvent ev) {
-        // Let the Action Bar have a chance at handling the shortcut
-        ActionBar ab = getSupportActionBar();
-        if (ab != null && ab.onKeyShortcut(keyCode, ev)) {
-            return true;
-        }
-
-        // If the panel is already prepared, then perform the shortcut using it.
-        boolean handled;
-        if (mPreparedPanel != null) {
-            handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev,
-                    Menu.FLAG_PERFORM_NO_CLOSE);
-            if (handled) {
-                if (mPreparedPanel != null) {
-                    mPreparedPanel.isHandled = true;
-                }
-                return true;
-            }
-        }
-
-        // If the panel is not prepared, then we may be trying to handle a shortcut key
-        // combination such as Control+C.  Temporarily prepare the panel then mark it
-        // unprepared again when finished to ensure that the panel will again be prepared
-        // the next time it is shown for real.
-        if (mPreparedPanel == null) {
-            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
-            preparePanel(st, ev);
-            handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE);
-            st.isPrepared = false;
-            if (handled) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    boolean dispatchKeyEvent(KeyEvent event) {
-        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
-            // If this is a MENU event, let the Activity have a go.
-            if (mOriginalWindowCallback.dispatchKeyEvent(event)) {
-                return true;
-            }
-        }
-
-        final int keyCode = event.getKeyCode();
-        final int action = event.getAction();
-        final boolean isDown = action == KeyEvent.ACTION_DOWN;
-
-        return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event);
-    }
-
-    boolean onKeyUp(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_MENU:
-                onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event);
-                return true;
-            case KeyEvent.KEYCODE_BACK:
-                final boolean wasLongPressBackDown = mLongPressBackDown;
-                mLongPressBackDown = false;
-
-                PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
-                if (st != null && st.isOpen) {
-                    if (!wasLongPressBackDown) {
-                        // Certain devices allow opening the options menu via a long press of the
-                        // back button. We should only close the open options menu if it wasn't
-                        // opened via a long press gesture.
-                        closePanel(st, true);
-                    }
-                    return true;
-                }
-                if (onBackPressed()) {
-                    return true;
-                }
-                break;
-        }
-        return false;
-    }
-
-    boolean onKeyDown(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_MENU:
-                onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event);
-                // We need to return true here and not let it bubble up to the Window.
-                // For empty menus, PhoneWindow's KEYCODE_BACK handling will steals all events,
-                // not allowing the Activity to call onBackPressed().
-                return true;
-            case KeyEvent.KEYCODE_BACK:
-                // Certain devices allow opening the options menu via a long press of the back
-                // button. We keep a record of whether the last event is from a long press.
-                mLongPressBackDown = (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
-                break;
-        }
-        return false;
-    }
-
-    @Override
-    public View createView(View parent, final String name, @NonNull Context context,
-            @NonNull AttributeSet attrs) {
-        if (mAppCompatViewInflater == null) {
-            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
-            String viewInflaterClassName =
-                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
-            if ((viewInflaterClassName == null)
-                    || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
-                // Either default class name or set explicitly to null. In both cases
-                // create the base inflater (no reflection)
-                mAppCompatViewInflater = new AppCompatViewInflater();
-            } else {
-                try {
-                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
-                    mAppCompatViewInflater =
-                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
-                                    .newInstance();
-                } catch (Throwable t) {
-                    Log.i(TAG, "Failed to instantiate custom view inflater "
-                            + viewInflaterClassName + ". Falling back to default.", t);
-                    mAppCompatViewInflater = new AppCompatViewInflater();
-                }
-            }
-        }
-
-        boolean inheritContext = false;
-        if (IS_PRE_LOLLIPOP) {
-            inheritContext = (attrs instanceof XmlPullParser)
-                    // If we have a XmlPullParser, we can detect where we are in the layout
-                    ? ((XmlPullParser) attrs).getDepth() > 1
-                    // Otherwise we have to use the old heuristic
-                    : shouldInheritContext((ViewParent) parent);
-        }
-
-        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
-                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
-                true, /* Read read app:theme as a fallback at all times for legacy reasons */
-                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
-        );
-    }
-
-    private boolean shouldInheritContext(ViewParent parent) {
-        if (parent == null) {
-            // The initial parent is null so just return false
-            return false;
-        }
-        final View windowDecor = mWindow.getDecorView();
-        while (true) {
-            if (parent == null) {
-                // Bingo. We've hit a view which has a null parent before being terminated from
-                // the loop. This is (most probably) because it's the root view in an inflation
-                // call, therefore we should inherit. This works as the inflated layout is only
-                // added to the hierarchy at the end of the inflate() call.
-                return true;
-            } else if (parent == windowDecor || !(parent instanceof View)
-                    || ViewCompat.isAttachedToWindow((View) parent)) {
-                // We have either hit the window's decor view, a parent which isn't a View
-                // (i.e. ViewRootImpl), or an attached view, so we know that the original parent
-                // is currently added to the view hierarchy. This means that it has not be
-                // inflated in the current inflate() call and we should not inherit the context.
-                return false;
-            }
-            parent = parent.getParent();
-        }
-    }
-
-    @Override
-    public void installViewFactory() {
-        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
-        if (layoutInflater.getFactory() == null) {
-            LayoutInflaterCompat.setFactory2(layoutInflater, this);
-        } else {
-            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
-                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
-                        + " so we can not install AppCompat's");
-            }
-        }
-    }
-
-    /**
-     * From {@link LayoutInflater.Factory2}.
-     */
-    @Override
-    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-        // First let the Activity's Factory try and inflate the view
-        final View view = callActivityOnCreateView(parent, name, context, attrs);
-        if (view != null) {
-            return view;
-        }
-
-        // If the Factory didn't handle it, let our createView() method try
-        return createView(parent, name, context, attrs);
-    }
-
-    /**
-     * From {@link LayoutInflater.Factory2}.
-     */
-    @Override
-    public View onCreateView(String name, Context context, AttributeSet attrs) {
-        return onCreateView(null, name, context, attrs);
-    }
-
-    View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
-        // Let the Activity's LayoutInflater.Factory try and handle it
-        if (mOriginalWindowCallback instanceof LayoutInflater.Factory) {
-            final View result = ((LayoutInflater.Factory) mOriginalWindowCallback)
-                    .onCreateView(name, context, attrs);
-            if (result != null) {
-                return result;
-            }
-        }
-        return null;
-    }
-
-    private void openPanel(final PanelFeatureState st, KeyEvent event) {
-        // Already open, return
-        if (st.isOpen || isDestroyed()) {
-            return;
-        }
-
-        // Don't open an options panel on xlarge devices.
-        // (The app should be using an action bar for menu items.)
-        if (st.featureId == FEATURE_OPTIONS_PANEL) {
-            Configuration config = mContext.getResources().getConfiguration();
-            boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
-                    == Configuration.SCREENLAYOUT_SIZE_XLARGE;
-            if (isXLarge) {
-                return;
-            }
-        }
-
-        Window.Callback cb = getWindowCallback();
-        if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
-            // Callback doesn't want the menu to open, reset any state
-            closePanel(st, true);
-            return;
-        }
-
-        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-        if (wm == null) {
-            return;
-        }
-
-        // Prepare panel (should have been done before, but just in case)
-        if (!preparePanel(st, event)) {
-            return;
-        }
-
-        int width = WRAP_CONTENT;
-        if (st.decorView == null || st.refreshDecorView) {
-            if (st.decorView == null) {
-                // Initialize the panel decor, this will populate st.decorView
-                if (!initializePanelDecor(st) || (st.decorView == null))
-                    return;
-            } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
-                // Decor needs refreshing, so remove its views
-                st.decorView.removeAllViews();
-            }
-
-            // This will populate st.shownPanelView
-            if (!initializePanelContent(st) || !st.hasPanelItems()) {
-                return;
-            }
-
-            ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
-            if (lp == null) {
-                lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
-            }
-
-            int backgroundResId = st.background;
-            st.decorView.setBackgroundResource(backgroundResId);
-
-            ViewParent shownPanelParent = st.shownPanelView.getParent();
-            if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
-                ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
-            }
-            st.decorView.addView(st.shownPanelView, lp);
-
-            /*
-             * Give focus to the view, if it or one of its children does not
-             * already have it.
-             */
-            if (!st.shownPanelView.hasFocus()) {
-                st.shownPanelView.requestFocus();
-            }
-        } else if (st.createdPanelView != null) {
-            // If we already had a panel view, carry width=MATCH_PARENT through
-            // as we did above when it was created.
-            ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
-            if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
-                width = MATCH_PARENT;
-            }
-        }
-
-        st.isHandled = false;
-
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                width, WRAP_CONTENT,
-                st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
-                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                PixelFormat.TRANSLUCENT);
-
-        lp.gravity = st.gravity;
-        lp.windowAnimations = st.windowAnimations;
-
-        wm.addView(st.decorView, lp);
-        st.isOpen = true;
-    }
-
-    private boolean initializePanelDecor(PanelFeatureState st) {
-        st.setStyle(getActionBarThemedContext());
-        st.decorView = new ListMenuDecorView(st.listPresenterContext);
-        st.gravity = Gravity.CENTER | Gravity.BOTTOM;
-        return true;
-    }
-
-    private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) {
-        if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu()
-                && (!ViewConfiguration.get(mContext).hasPermanentMenuKey()
-                        || mDecorContentParent.isOverflowMenuShowPending())) {
-
-            final Window.Callback cb = getWindowCallback();
-
-            if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
-                if (cb != null && !isDestroyed()) {
-                    // If we have a menu invalidation pending, do it now.
-                    if (mInvalidatePanelMenuPosted &&
-                            (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) {
-                        mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
-                        mInvalidatePanelMenuRunnable.run();
-                    }
-
-                    final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
-
-                    // If we don't have a menu or we're waiting for a full content refresh,
-                    // forget it. This is a lingering event that no longer matters.
-                    if (st.menu != null && !st.refreshMenuContent &&
-                            cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
-                        cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, st.menu);
-                        mDecorContentParent.showOverflowMenu();
-                    }
-                }
-            } else {
-                mDecorContentParent.hideOverflowMenu();
-                if (!isDestroyed()) {
-                    final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
-                    cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, st.menu);
-                }
-            }
-            return;
-        }
-
-        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
-
-        st.refreshDecorView = true;
-        closePanel(st, false);
-
-        openPanel(st, null);
-    }
-
-    private boolean initializePanelMenu(final PanelFeatureState st) {
-        Context context = mContext;
-
-        // If we have an action bar, initialize the menu with the right theme.
-        if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR) &&
-                mDecorContentParent != null) {
-            final TypedValue outValue = new TypedValue();
-            final Resources.Theme baseTheme = context.getTheme();
-            baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
-
-            Resources.Theme widgetTheme = null;
-            if (outValue.resourceId != 0) {
-                widgetTheme = context.getResources().newTheme();
-                widgetTheme.setTo(baseTheme);
-                widgetTheme.applyStyle(outValue.resourceId, true);
-                widgetTheme.resolveAttribute(
-                        R.attr.actionBarWidgetTheme, outValue, true);
-            } else {
-                baseTheme.resolveAttribute(
-                        R.attr.actionBarWidgetTheme, outValue, true);
-            }
-
-            if (outValue.resourceId != 0) {
-                if (widgetTheme == null) {
-                    widgetTheme = context.getResources().newTheme();
-                    widgetTheme.setTo(baseTheme);
-                }
-                widgetTheme.applyStyle(outValue.resourceId, true);
-            }
-
-            if (widgetTheme != null) {
-                context = new ContextThemeWrapper(context, 0);
-                context.getTheme().setTo(widgetTheme);
-            }
-        }
-
-        final MenuBuilder menu = new MenuBuilder(context);
-        menu.setCallback(this);
-        st.setMenu(menu);
-
-        return true;
-    }
-
-    private boolean initializePanelContent(PanelFeatureState st) {
-        if (st.createdPanelView != null) {
-            st.shownPanelView = st.createdPanelView;
-            return true;
-        }
-
-        if (st.menu == null) {
-            return false;
-        }
-
-        if (mPanelMenuPresenterCallback == null) {
-            mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
-        }
-
-        MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback);
-
-        st.shownPanelView = (View) menuView;
-
-        return st.shownPanelView != null;
-    }
-
-    private boolean preparePanel(PanelFeatureState st, KeyEvent event) {
-        if (isDestroyed()) {
-            return false;
-        }
-
-        // Already prepared (isPrepared will be reset to false later)
-        if (st.isPrepared) {
-            return true;
-        }
-
-        if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
-            // Another Panel is prepared and possibly open, so close it
-            closePanel(mPreparedPanel, false);
-        }
-
-        final Window.Callback cb = getWindowCallback();
-
-        if (cb != null) {
-            st.createdPanelView = cb.onCreatePanelView(st.featureId);
-        }
-
-        final boolean isActionBarMenu =
-                (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR);
-
-        if (isActionBarMenu && mDecorContentParent != null) {
-            // Enforce ordering guarantees around events so that the action bar never
-            // dispatches menu-related events before the panel is prepared.
-            mDecorContentParent.setMenuPrepared();
-        }
-
-        if (st.createdPanelView == null &&
-                (!isActionBarMenu || !(peekSupportActionBar() instanceof ToolbarActionBar))) {
-            // Since ToolbarActionBar handles the list options menu itself, we only want to
-            // init this menu panel if we're not using a TAB.
-            if (st.menu == null || st.refreshMenuContent) {
-                if (st.menu == null) {
-                    if (!initializePanelMenu(st) || (st.menu == null)) {
-                        return false;
-                    }
-                }
-
-                if (isActionBarMenu && mDecorContentParent != null) {
-                    if (mActionMenuPresenterCallback == null) {
-                        mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
-                    }
-                    mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
-                }
-
-                // Creating the panel menu will involve a lot of manipulation;
-                // don't dispatch change events to presenters until we're done.
-                st.menu.stopDispatchingItemsChanged();
-                if (!cb.onCreatePanelMenu(st.featureId, st.menu)) {
-                    // Ditch the menu created above
-                    st.setMenu(null);
-
-                    if (isActionBarMenu && mDecorContentParent != null) {
-                        // Don't show it in the action bar either
-                        mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
-                    }
-
-                    return false;
-                }
-
-                st.refreshMenuContent = false;
-            }
-
-            // Preparing the panel menu can involve a lot of manipulation;
-            // don't dispatch change events to presenters until we're done.
-            st.menu.stopDispatchingItemsChanged();
-
-            // Restore action view state before we prepare. This gives apps
-            // an opportunity to override frozen/restored state in onPrepare.
-            if (st.frozenActionViewState != null) {
-                st.menu.restoreActionViewStates(st.frozenActionViewState);
-                st.frozenActionViewState = null;
-            }
-
-            // Callback and return if the callback does not want to show the menu
-            if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
-                if (isActionBarMenu && mDecorContentParent != null) {
-                    // The app didn't want to show the menu for now but it still exists.
-                    // Clear it out of the action bar.
-                    mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
-                }
-                st.menu.startDispatchingItemsChanged();
-                return false;
-            }
-
-            // Set the proper keymap
-            KeyCharacterMap kmap = KeyCharacterMap.load(
-                    event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
-            st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
-            st.menu.setQwertyMode(st.qwertyMode);
-            st.menu.startDispatchingItemsChanged();
-        }
-
-        // Set other state
-        st.isPrepared = true;
-        st.isHandled = false;
-        mPreparedPanel = st;
-
-        return true;
-    }
-
-    void checkCloseActionMenu(MenuBuilder menu) {
-        if (mClosingActionMenu) {
-            return;
-        }
-
-        mClosingActionMenu = true;
-        mDecorContentParent.dismissPopups();
-        Window.Callback cb = getWindowCallback();
-        if (cb != null && !isDestroyed()) {
-            cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, menu);
-        }
-        mClosingActionMenu = false;
-    }
-
-    void closePanel(int featureId) {
-        closePanel(getPanelState(featureId, true), true);
-    }
-
-    void closePanel(PanelFeatureState st, boolean doCallback) {
-        if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
-                mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
-            checkCloseActionMenu(st.menu);
-            return;
-        }
-
-        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-        if (wm != null && st.isOpen && st.decorView != null) {
-            wm.removeView(st.decorView);
-
-            if (doCallback) {
-                callOnPanelClosed(st.featureId, st, null);
-            }
-        }
-
-        st.isPrepared = false;
-        st.isHandled = false;
-        st.isOpen = false;
-
-        // This view is no longer shown, so null it out
-        st.shownPanelView = null;
-
-        // Next time the menu opens, it should not be in expanded mode, so
-        // force a refresh of the decor
-        st.refreshDecorView = true;
-
-        if (mPreparedPanel == st) {
-            mPreparedPanel = null;
-        }
-    }
-
-    private boolean onKeyDownPanel(int featureId, KeyEvent event) {
-        if (event.getRepeatCount() == 0) {
-            PanelFeatureState st = getPanelState(featureId, true);
-            if (!st.isOpen) {
-                return preparePanel(st, event);
-            }
-        }
-
-        return false;
-    }
-
-    private boolean onKeyUpPanel(int featureId, KeyEvent event) {
-        if (mActionMode != null) {
-            return false;
-        }
-
-        boolean handled = false;
-        final PanelFeatureState st = getPanelState(featureId, true);
-        if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
-                mDecorContentParent.canShowOverflowMenu() &&
-                !ViewConfiguration.get(mContext).hasPermanentMenuKey()) {
-            if (!mDecorContentParent.isOverflowMenuShowing()) {
-                if (!isDestroyed() && preparePanel(st, event)) {
-                    handled = mDecorContentParent.showOverflowMenu();
-                }
-            } else {
-                handled = mDecorContentParent.hideOverflowMenu();
-            }
-        } else {
-            if (st.isOpen || st.isHandled) {
-                // Play the sound effect if the user closed an open menu (and not if
-                // they just released a menu shortcut)
-                handled = st.isOpen;
-                // Close menu
-                closePanel(st, true);
-            } else if (st.isPrepared) {
-                boolean show = true;
-                if (st.refreshMenuContent) {
-                    // Something may have invalidated the menu since we prepared it.
-                    // Re-prepare it to refresh.
-                    st.isPrepared = false;
-                    show = preparePanel(st, event);
-                }
-
-                if (show) {
-                    // Show menu
-                    openPanel(st, event);
-                    handled = true;
-                }
-            }
-        }
-
-        if (handled) {
-            AudioManager audioManager = (AudioManager) mContext.getSystemService(
-                    Context.AUDIO_SERVICE);
-            if (audioManager != null) {
-                audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
-            } else {
-                Log.w(TAG, "Couldn't get audio manager");
-            }
-        }
-        return handled;
-    }
-
-    void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
-        // Try to get a menu
-        if (menu == null) {
-            // Need a panel to grab the menu, so try to get that
-            if (panel == null) {
-                if ((featureId >= 0) && (featureId < mPanels.length)) {
-                    panel = mPanels[featureId];
-                }
-            }
-
-            if (panel != null) {
-                // menu still may be null, which is okay--we tried our best
-                menu = panel.menu;
-            }
-        }
-
-        // If the panel is not open, do not callback
-        if ((panel != null) && (!panel.isOpen))
-            return;
-
-        if (!isDestroyed()) {
-            // We need to be careful which callback we dispatch the call to. We can not dispatch
-            // this to the Window's callback since that will call back into this method and cause a
-            // crash. Instead we need to dispatch down to the original Activity/Dialog/etc.
-            mOriginalWindowCallback.onPanelClosed(featureId, menu);
-        }
-    }
-
-    PanelFeatureState findMenuPanel(Menu menu) {
-        final PanelFeatureState[] panels = mPanels;
-        final int N = panels != null ? panels.length : 0;
-        for (int i = 0; i < N; i++) {
-            final PanelFeatureState panel = panels[i];
-            if (panel != null && panel.menu == menu) {
-                return panel;
-            }
-        }
-        return null;
-    }
-
-    protected PanelFeatureState getPanelState(int featureId, boolean required) {
-        PanelFeatureState[] ar;
-        if ((ar = mPanels) == null || ar.length <= featureId) {
-            PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
-            if (ar != null) {
-                System.arraycopy(ar, 0, nar, 0, ar.length);
-            }
-            mPanels = ar = nar;
-        }
-
-        PanelFeatureState st = ar[featureId];
-        if (st == null) {
-            ar[featureId] = st = new PanelFeatureState(featureId);
-        }
-        return st;
-    }
-
-    private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
-            int flags) {
-        if (event.isSystem()) {
-            return false;
-        }
-
-        boolean handled = false;
-
-        // Only try to perform menu shortcuts if preparePanel returned true (possible false
-        // return value from application not wanting to show the menu).
-        if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
-            // The menu is prepared now, perform the shortcut on it
-            handled = st.menu.performShortcut(keyCode, event, flags);
-        }
-
-        if (handled) {
-            // Only close down the menu if we don't have an action bar keeping it open.
-            if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) {
-                closePanel(st, true);
-            }
-        }
-
-        return handled;
-    }
-
-    private void invalidatePanelMenu(int featureId) {
-        mInvalidatePanelMenuFeatures |= 1 << featureId;
-
-        if (!mInvalidatePanelMenuPosted) {
-            ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable);
-            mInvalidatePanelMenuPosted = true;
-        }
-    }
-
-    void doInvalidatePanelMenu(int featureId) {
-        PanelFeatureState st = getPanelState(featureId, true);
-        Bundle savedActionViewStates = null;
-        if (st.menu != null) {
-            savedActionViewStates = new Bundle();
-            st.menu.saveActionViewStates(savedActionViewStates);
-            if (savedActionViewStates.size() > 0) {
-                st.frozenActionViewState = savedActionViewStates;
-            }
-            // This will be started again when the panel is prepared.
-            st.menu.stopDispatchingItemsChanged();
-            st.menu.clear();
-        }
-        st.refreshMenuContent = true;
-        st.refreshDecorView = true;
-
-        // Prepare the options panel if we have an action bar
-        if ((featureId == FEATURE_SUPPORT_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
-                && mDecorContentParent != null) {
-            st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
-            if (st != null) {
-                st.isPrepared = false;
-                preparePanel(st, null);
-            }
-        }
-    }
-
-    /**
-     * Updates the status bar guard
-     *
-     * @param insetTop the current top system window inset
-     * @return the new top system window inset
-     */
-    int updateStatusGuard(int insetTop) {
-        boolean showStatusGuard = false;
-        // Show the status guard when the non-overlay contextual action bar is showing
-        if (mActionModeView != null) {
-            if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
-                ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)
-                        mActionModeView.getLayoutParams();
-                boolean mlpChanged = false;
-
-                if (mActionModeView.isShown()) {
-                    if (mTempRect1 == null) {
-                        mTempRect1 = new Rect();
-                        mTempRect2 = new Rect();
-                    }
-                    final Rect insets = mTempRect1;
-                    final Rect localInsets = mTempRect2;
-                    insets.set(0, insetTop, 0, 0);
-
-                    ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets);
-                    final int newMargin = localInsets.top == 0 ? insetTop : 0;
-                    if (mlp.topMargin != newMargin) {
-                        mlpChanged = true;
-                        mlp.topMargin = insetTop;
-
-                        if (mStatusGuard == null) {
-                            mStatusGuard = new View(mContext);
-                            mStatusGuard.setBackgroundColor(mContext.getResources()
-                                    .getColor(R.color.abc_input_method_navigation_guard));
-                            mSubDecor.addView(mStatusGuard, -1,
-                                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                            insetTop));
-                        } else {
-                            ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams();
-                            if (lp.height != insetTop) {
-                                lp.height = insetTop;
-                                mStatusGuard.setLayoutParams(lp);
-                            }
-                        }
-                    }
-
-                    // The action mode's theme may differ from the app, so
-                    // always show the status guard above it.
-                    showStatusGuard = mStatusGuard != null;
-
-                    // We only need to consume the insets if the action
-                    // mode is overlaid on the app content (e.g. it's
-                    // sitting in a FrameLayout, see
-                    // screen_simple_overlay_action_mode.xml).
-                    if (!mOverlayActionMode && showStatusGuard) {
-                        insetTop = 0;
-                    }
-                } else {
-                    // reset top margin
-                    if (mlp.topMargin != 0) {
-                        mlpChanged = true;
-                        mlp.topMargin = 0;
-                    }
-                }
-                if (mlpChanged) {
-                    mActionModeView.setLayoutParams(mlp);
-                }
-            }
-        }
-        if (mStatusGuard != null) {
-            mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
-        }
-
-        return insetTop;
-    }
-
-    private void throwFeatureRequestIfSubDecorInstalled() {
-        if (mSubDecorInstalled) {
-            throw new AndroidRuntimeException(
-                    "Window feature must be requested before adding content");
-        }
-    }
-
-    private int sanitizeWindowFeatureId(int featureId) {
-        if (featureId == WindowCompat.FEATURE_ACTION_BAR) {
-            Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR"
-                    + " id when requesting this feature.");
-            return FEATURE_SUPPORT_ACTION_BAR;
-        } else if (featureId == WindowCompat.FEATURE_ACTION_BAR_OVERLAY) {
-            Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR_OVERLAY"
-                    + " id when requesting this feature.");
-            return FEATURE_SUPPORT_ACTION_BAR_OVERLAY;
-        }
-        // Else we'll just return the original id
-        return featureId;
-    }
-
-    ViewGroup getSubDecor() {
-        return mSubDecor;
-    }
-
-    void dismissPopups() {
-        if (mDecorContentParent != null) {
-            mDecorContentParent.dismissPopups();
-        }
-
-        if (mActionModePopup != null) {
-            mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
-            if (mActionModePopup.isShowing()) {
-                try {
-                    mActionModePopup.dismiss();
-                } catch (IllegalArgumentException e) {
-                    // Pre-v18, there are times when the Window will remove the popup before us.
-                    // In these cases we need to swallow the resulting exception.
-                }
-            }
-            mActionModePopup = null;
-        }
-        endOnGoingFadeAnimation();
-
-        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
-        if (st != null && st.menu != null) {
-            st.menu.close();
-        }
-    }
-
-    /**
-     * Clears out internal reference when the action mode is destroyed.
-     */
-    class ActionModeCallbackWrapperV9 implements ActionMode.Callback {
-        private ActionMode.Callback mWrapped;
-
-        public ActionModeCallbackWrapperV9(ActionMode.Callback wrapped) {
-            mWrapped = wrapped;
-        }
-
-        @Override
-        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-            return mWrapped.onCreateActionMode(mode, menu);
-        }
-
-        @Override
-        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-            return mWrapped.onPrepareActionMode(mode, menu);
-        }
-
-        @Override
-        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-            return mWrapped.onActionItemClicked(mode, item);
-        }
-
-        @Override
-        public void onDestroyActionMode(ActionMode mode) {
-            mWrapped.onDestroyActionMode(mode);
-            if (mActionModePopup != null) {
-                mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
-            }
-
-            if (mActionModeView != null) {
-                endOnGoingFadeAnimation();
-                mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f);
-                mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(View view) {
-                        mActionModeView.setVisibility(View.GONE);
-                        if (mActionModePopup != null) {
-                            mActionModePopup.dismiss();
-                        } else if (mActionModeView.getParent() instanceof View) {
-                            ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
-                        }
-                        mActionModeView.removeAllViews();
-                        mFadeAnim.setListener(null);
-                        mFadeAnim = null;
-                    }
-                });
-            }
-            if (mAppCompatCallback != null) {
-                mAppCompatCallback.onSupportActionModeFinished(mActionMode);
-            }
-            mActionMode = null;
-        }
-    }
-
-    private final class PanelMenuPresenterCallback implements MenuPresenter.Callback {
-        PanelMenuPresenterCallback() {
-        }
-
-        @Override
-        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-            final Menu parentMenu = menu.getRootMenu();
-            final boolean isSubMenu = parentMenu != menu;
-            final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
-            if (panel != null) {
-                if (isSubMenu) {
-                    callOnPanelClosed(panel.featureId, panel, parentMenu);
-                    closePanel(panel, true);
-                } else {
-                    // Close the panel and only do the callback if the menu is being
-                    // closed completely, not if opening a sub menu
-                    closePanel(panel, allMenusAreClosing);
-                }
-            }
-        }
-
-        @Override
-        public boolean onOpenSubMenu(MenuBuilder subMenu) {
-            if (subMenu == null && mHasActionBar) {
-                Window.Callback cb = getWindowCallback();
-                if (cb != null && !isDestroyed()) {
-                    cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu);
-                }
-            }
-            return true;
-        }
-    }
-
-    private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
-        ActionMenuPresenterCallback() {
-        }
-
-        @Override
-        public boolean onOpenSubMenu(MenuBuilder subMenu) {
-            Window.Callback cb = getWindowCallback();
-            if (cb != null) {
-                cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu);
-            }
-            return true;
-        }
-
-        @Override
-        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-            checkCloseActionMenu(menu);
-        }
-    }
-
-    protected static final class PanelFeatureState {
-
-        /** Feature ID for this panel. */
-        int featureId;
-
-        int background;
-
-        int gravity;
-
-        int x;
-
-        int y;
-
-        int windowAnimations;
-
-        /** Dynamic state of the panel. */
-        ViewGroup decorView;
-
-        /** The panel that we are actually showing. */
-        View shownPanelView;
-
-        /** The panel that was returned by onCreatePanelView(). */
-        View createdPanelView;
-
-        /** Use {@link #setMenu} to set this. */
-        MenuBuilder menu;
-
-        ListMenuPresenter listMenuPresenter;
-
-        Context listPresenterContext;
-
-        /**
-         * Whether the panel has been prepared (see
-         * {@link #preparePanel}).
-         */
-        boolean isPrepared;
-
-        /**
-         * Whether an item's action has been performed. This happens in obvious
-         * scenarios (user clicks on menu item), but can also happen with
-         * chording menu+(shortcut key).
-         */
-        boolean isHandled;
-
-        boolean isOpen;
-
-        public boolean qwertyMode;
-
-        boolean refreshDecorView;
-
-        boolean refreshMenuContent;
-
-        boolean wasLastOpen;
-
-        /**
-         * Contains the state of the menu when told to freeze.
-         */
-        Bundle frozenMenuState;
-
-        /**
-         * Contains the state of associated action views when told to freeze.
-         * These are saved across invalidations.
-         */
-        Bundle frozenActionViewState;
-
-        PanelFeatureState(int featureId) {
-            this.featureId = featureId;
-
-            refreshDecorView = false;
-        }
-
-        public boolean hasPanelItems() {
-            if (shownPanelView == null) return false;
-            if (createdPanelView != null) return true;
-
-            return listMenuPresenter.getAdapter().getCount() > 0;
-        }
-
-        /**
-         * Unregister and free attached MenuPresenters. They will be recreated as needed.
-         */
-        public void clearMenuPresenters() {
-            if (menu != null) {
-                menu.removeMenuPresenter(listMenuPresenter);
-            }
-            listMenuPresenter = null;
-        }
-
-        void setStyle(Context context) {
-            final TypedValue outValue = new TypedValue();
-            final Resources.Theme widgetTheme = context.getResources().newTheme();
-            widgetTheme.setTo(context.getTheme());
-
-            // First apply the actionBarPopupTheme
-            widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true);
-            if (outValue.resourceId != 0) {
-                widgetTheme.applyStyle(outValue.resourceId, true);
-            }
-
-            // Now apply the panelMenuListTheme
-            widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
-            if (outValue.resourceId != 0) {
-                widgetTheme.applyStyle(outValue.resourceId, true);
-            } else {
-                widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true);
-            }
-
-            context = new ContextThemeWrapper(context, 0);
-            context.getTheme().setTo(widgetTheme);
-
-            listPresenterContext = context;
-
-            TypedArray a = context.obtainStyledAttributes(R.styleable.AppCompatTheme);
-            background = a.getResourceId(
-                    R.styleable.AppCompatTheme_panelBackground, 0);
-            windowAnimations = a.getResourceId(
-                    R.styleable.AppCompatTheme_android_windowAnimationStyle, 0);
-            a.recycle();
-        }
-
-        void setMenu(MenuBuilder menu) {
-            if (menu == this.menu) return;
-
-            if (this.menu != null) {
-                this.menu.removeMenuPresenter(listMenuPresenter);
-            }
-            this.menu = menu;
-            if (menu != null) {
-                if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter);
-            }
-        }
-
-        MenuView getListMenuView(MenuPresenter.Callback cb) {
-            if (menu == null) return null;
-
-            if (listMenuPresenter == null) {
-                listMenuPresenter = new ListMenuPresenter(listPresenterContext,
-                        R.layout.abc_list_menu_item_layout);
-                listMenuPresenter.setCallback(cb);
-                menu.addMenuPresenter(listMenuPresenter);
-            }
-
-            MenuView result = listMenuPresenter.getMenuView(decorView);
-
-            return result;
-        }
-
-        Parcelable onSaveInstanceState() {
-            SavedState savedState = new SavedState();
-            savedState.featureId = featureId;
-            savedState.isOpen = isOpen;
-
-            if (menu != null) {
-                savedState.menuState = new Bundle();
-                menu.savePresenterStates(savedState.menuState);
-            }
-
-            return savedState;
-        }
-
-        void onRestoreInstanceState(Parcelable state) {
-            SavedState savedState = (SavedState) state;
-            featureId = savedState.featureId;
-            wasLastOpen = savedState.isOpen;
-            frozenMenuState = savedState.menuState;
-
-            shownPanelView = null;
-            decorView = null;
-        }
-
-        void applyFrozenState() {
-            if (menu != null && frozenMenuState != null) {
-                menu.restorePresenterStates(frozenMenuState);
-                frozenMenuState = null;
-            }
-        }
-
-        private static class SavedState implements Parcelable {
-            int featureId;
-            boolean isOpen;
-            Bundle menuState;
-
-            SavedState() {
-            }
-
-            @Override
-            public int describeContents() {
-                return 0;
-            }
-
-            @Override
-            public void writeToParcel(Parcel dest, int flags) {
-                dest.writeInt(featureId);
-                dest.writeInt(isOpen ? 1 : 0);
-
-                if (isOpen) {
-                    dest.writeBundle(menuState);
-                }
-            }
-
-            static SavedState readFromParcel(Parcel source, ClassLoader loader) {
-                SavedState savedState = new SavedState();
-                savedState.featureId = source.readInt();
-                savedState.isOpen = source.readInt() == 1;
-
-                if (savedState.isOpen) {
-                    savedState.menuState = source.readBundle(loader);
-                }
-
-                return savedState;
-            }
-
-            public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
-                @Override
-                public SavedState createFromParcel(Parcel in, ClassLoader loader) {
-                    return readFromParcel(in, loader);
-                }
-
-                @Override
-                public SavedState createFromParcel(Parcel in) {
-                    return readFromParcel(in, null);
-                }
-
-                @Override
-                public SavedState[] newArray(int size) {
-                    return new SavedState[size];
-                }
-            };
-        }
-    }
-
-    private class ListMenuDecorView extends ContentFrameLayout {
-        public ListMenuDecorView(Context context) {
-            super(context);
-        }
-
-        @Override
-        public boolean dispatchKeyEvent(KeyEvent event) {
-            return AppCompatDelegateImplV9.this.dispatchKeyEvent(event)
-                    || super.dispatchKeyEvent(event);
-        }
-
-        @Override
-        public boolean onInterceptTouchEvent(MotionEvent event) {
-            int action = event.getAction();
-            if (action == MotionEvent.ACTION_DOWN) {
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                if (isOutOfBounds(x, y)) {
-                    closePanel(Window.FEATURE_OPTIONS_PANEL);
-                    return true;
-                }
-            }
-            return super.onInterceptTouchEvent(event);
-        }
-
-        @Override
-        public void setBackgroundResource(int resid) {
-            setBackgroundDrawable(AppCompatResources.getDrawable(getContext(), resid));
-        }
-
-        private boolean isOutOfBounds(int x, int y) {
-            return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5);
-        }
-    }
-}
diff --git a/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java b/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java
index 21ba27e..8aa54d1 100644
--- a/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java
+++ b/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchHelper.java
@@ -1375,8 +1375,6 @@
                 | ((START | END) << DIRECTION_FLAG_COUNT)
                 | ((START | END) << (2 * DIRECTION_FLAG_COUNT));
 
-        private static final ItemTouchUIUtil sUICallback;
-
         private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT
                 | ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT)
                 | ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
@@ -1403,14 +1401,6 @@
 
         private int mCachedMaxScrollSpeed = -1;
 
-        static {
-            if (Build.VERSION.SDK_INT >= 21) {
-                sUICallback = new ItemTouchUIUtilImpl.Api21Impl();
-            } else {
-                sUICallback = new ItemTouchUIUtilImpl.BaseImpl();
-            }
-        }
-
         /**
          * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for
          * visual
@@ -1454,7 +1444,7 @@
         @SuppressWarnings("WeakerAccess")
         @NonNull
         public static ItemTouchUIUtil getDefaultUIUtil() {
-            return sUICallback;
+            return ItemTouchUIUtilImpl.INSTANCE;
         }
 
         /**
@@ -1884,7 +1874,7 @@
          */
         public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState) {
             if (viewHolder != null) {
-                sUICallback.onSelected(viewHolder.itemView);
+                ItemTouchUIUtilImpl.INSTANCE.onSelected(viewHolder.itemView);
             }
         }
 
@@ -2026,7 +2016,7 @@
          * @param viewHolder   The View that was interacted by the user.
          */
         public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) {
-            sUICallback.clearView(viewHolder.itemView);
+            ItemTouchUIUtilImpl.INSTANCE.clearView(viewHolder.itemView);
         }
 
         /**
@@ -2059,8 +2049,8 @@
         public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
                 @NonNull ViewHolder viewHolder,
                 float dX, float dY, int actionState, boolean isCurrentlyActive) {
-            sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
-                    isCurrentlyActive);
+            ItemTouchUIUtilImpl.INSTANCE.onDraw(c, recyclerView, viewHolder.itemView, dX, dY,
+                    actionState, isCurrentlyActive);
         }
 
         /**
@@ -2093,8 +2083,8 @@
         public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
                 ViewHolder viewHolder,
                 float dX, float dY, int actionState, boolean isCurrentlyActive) {
-            sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
-                    isCurrentlyActive);
+            ItemTouchUIUtilImpl.INSTANCE.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY,
+                    actionState, isCurrentlyActive);
         }
 
         /**
diff --git a/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java b/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java
index c41f9cb..f41df9b 100644
--- a/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java
+++ b/v7/recyclerview/src/main/java/androidx/recyclerview/widget/ItemTouchUIUtilImpl.java
@@ -17,6 +17,7 @@
 package androidx.recyclerview.widget;
 
 import android.graphics.Canvas;
+import android.os.Build;
 import android.view.View;
 
 import androidx.core.view.ViewCompat;
@@ -26,11 +27,13 @@
  * Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them
  * public API, which is not desired in this case.
  */
-class ItemTouchUIUtilImpl {
-    static class Api21Impl extends BaseImpl {
-        @Override
-        public void onDraw(Canvas c, RecyclerView recyclerView, View view,
-                float dX, float dY, int actionState, boolean isCurrentlyActive) {
+class ItemTouchUIUtilImpl implements ItemTouchUIUtil {
+    static final ItemTouchUIUtil INSTANCE =  new ItemTouchUIUtilImpl();
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
+            int actionState, boolean isCurrentlyActive) {
+        if (Build.VERSION.SDK_INT >= 21) {
             if (isCurrentlyActive) {
                 Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
                 if (originalElevation == null) {
@@ -40,60 +43,48 @@
                     view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
                 }
             }
-            super.onDraw(c, recyclerView, view, dX, dY, actionState, isCurrentlyActive);
         }
 
-        private float findMaxElevation(RecyclerView recyclerView, View itemView) {
-            final int childCount = recyclerView.getChildCount();
-            float max = 0;
-            for (int i = 0; i < childCount; i++) {
-                final View child = recyclerView.getChildAt(i);
-                if (child == itemView) {
-                    continue;
-                }
-                final float elevation = ViewCompat.getElevation(child);
-                if (elevation > max) {
-                    max = elevation;
-                }
+        view.setTranslationX(dX);
+        view.setTranslationY(dY);
+    }
+
+    private static float findMaxElevation(RecyclerView recyclerView, View itemView) {
+        final int childCount = recyclerView.getChildCount();
+        float max = 0;
+        for (int i = 0; i < childCount; i++) {
+            final View child = recyclerView.getChildAt(i);
+            if (child == itemView) {
+                continue;
             }
-            return max;
+            final float elevation = ViewCompat.getElevation(child);
+            if (elevation > max) {
+                max = elevation;
+            }
         }
+        return max;
+    }
 
-        @Override
-        public void clearView(View view) {
+    @Override
+    public void onDrawOver(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
+            int actionState, boolean isCurrentlyActive) {
+    }
+
+    @Override
+    public void clearView(View view) {
+        if (Build.VERSION.SDK_INT >= 21) {
             final Object tag = view.getTag(R.id.item_touch_helper_previous_elevation);
             if (tag != null && tag instanceof Float) {
                 ViewCompat.setElevation(view, (Float) tag);
             }
             view.setTag(R.id.item_touch_helper_previous_elevation, null);
-            super.clearView(view);
         }
+
+        view.setTranslationX(0f);
+        view.setTranslationY(0f);
     }
 
-    static class BaseImpl implements ItemTouchUIUtil {
-
-        @Override
-        public void clearView(View view) {
-            view.setTranslationX(0f);
-            view.setTranslationY(0f);
-        }
-
-        @Override
-        public void onSelected(View view) {
-
-        }
-
-        @Override
-        public void onDraw(Canvas c, RecyclerView recyclerView, View view,
-                float dX, float dY, int actionState, boolean isCurrentlyActive) {
-            view.setTranslationX(dX);
-            view.setTranslationY(dY);
-        }
-
-        @Override
-        public void onDrawOver(Canvas c, RecyclerView recyclerView,
-                View view, float dX, float dY, int actionState, boolean isCurrentlyActive) {
-
-        }
+    @Override
+    public void onSelected(View view) {
     }
 }
diff --git a/viewpager2/src/androidTest/AndroidManifest.xml b/viewpager2/src/androidTest/AndroidManifest.xml
index d1332fa..8e7596b 100755
--- a/viewpager2/src/androidTest/AndroidManifest.xml
+++ b/viewpager2/src/androidTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
     <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
 
     <application android:supportsRtl="true">
-        <activity android:name="androidx.viewpager2.widget.TestActivity"/>
+        <activity android:name="androidx.viewpager2.widget.swipe.ViewAdapterActivity"/>
+        <activity android:name="androidx.viewpager2.widget.swipe.FragmentAdapterActivity"/>
     </application>
 </manifest>
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BasicTest.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BasicTest.java
new file mode 100644
index 0000000..483317b
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BasicTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 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 androidx.viewpager2.widget;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.arrayWithSize;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import java.util.UUID;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BasicTest {
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    @Test
+    public void test_recyclerViewAdapter_pageFillEnforced() {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage(
+                "Item's root view must fill the whole ViewPager2 (use match_parent)");
+
+        ViewPager2 viewPager = new ViewPager2(InstrumentationRegistry.getContext());
+        viewPager.setAdapter(new RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+            @NonNull
+            @Override
+            public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+                    int viewType) {
+                View view = new View(parent.getContext());
+                view.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); // arbitrary fixed size
+                return new RecyclerView.ViewHolder(view) {
+                };
+            }
+
+            @Override
+            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+                // do nothing
+            }
+
+            @Override
+            public int getItemCount() {
+                return 1;
+            }
+        });
+
+        viewPager.measure(0, 0); // equivalent of unspecified
+    }
+
+    @Test
+    public void test_childrenNotAllowed() throws Exception {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("ViewPager2 does not support direct child views");
+
+        Context context = InstrumentationRegistry.getContext();
+        ViewPager2 viewPager = new ViewPager2(context);
+        viewPager.addView(new View(context));
+    }
+
+    @Test
+    public void test_saveStateParcel_createRestore() throws Throwable {
+        // given
+        Bundle superState = createIntBundle(42);
+        ViewPager2.SavedState state = new ViewPager2.SavedState(superState);
+        state.mRecyclerViewId = 700;
+        state.mAdapterState = new Parcelable[]{createIntBundle(1), createIntBundle(2),
+                createIntBundle(3)};
+
+        // when
+        Parcel parcel = Parcel.obtain();
+        state.writeToParcel(parcel, 0);
+        final String parcelSuffix = UUID.randomUUID().toString();
+        parcel.writeString(parcelSuffix); // to verify parcel boundaries
+        parcel.setDataPosition(0);
+        ViewPager2.SavedState recreatedState = ViewPager2.SavedState.CREATOR.createFromParcel(
+                parcel);
+
+        // then
+        assertThat("Parcel reading should not go out of bounds", parcel.readString(),
+                equalTo(parcelSuffix));
+        assertThat("All of the parcel should be read", parcel.dataAvail(), equalTo(0));
+        assertThat(recreatedState.mRecyclerViewId, equalTo(700));
+        assertThat(recreatedState.mAdapterState, arrayWithSize(3));
+        assertThat((int) ((Bundle) recreatedState.getSuperState()).get("key"), equalTo(42));
+        assertThat((int) ((Bundle) recreatedState.mAdapterState[0]).get("key"), equalTo(1));
+        assertThat((int) ((Bundle) recreatedState.mAdapterState[1]).get("key"), equalTo(2));
+        assertThat((int) ((Bundle) recreatedState.mAdapterState[2]).get("key"), equalTo(3));
+    }
+
+    private Bundle createIntBundle(int value) {
+        Bundle bundle = new Bundle(1);
+        bundle.putInt("key", value);
+        return bundle;
+    }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.java
new file mode 100644
index 0000000..95bf351
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2018 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 androidx.viewpager2.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.view.View.OVER_SCROLL_NEVER;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.os.Build;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.testutils.FragmentActivityUtils;
+import androidx.viewpager2.test.R;
+import androidx.viewpager2.widget.swipe.BaseActivity;
+import androidx.viewpager2.widget.swipe.FragmentAdapterActivity;
+import androidx.viewpager2.widget.swipe.PageSwiper;
+import androidx.viewpager2.widget.swipe.ViewAdapterActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+@LargeTest
+@RunWith(Parameterized.class)
+public class SwipeTest {
+    private static final List<Class<? extends BaseActivity>> TEST_ACTIVITIES_ALL = asList(
+            ViewAdapterActivity.class, FragmentAdapterActivity.class);
+    private static final Set<Integer> NO_CONFIG_CHANGES = Collections.emptySet();
+    private static final List<Pair<Integer, Integer>> NO_MUTATIONS = Collections.emptyList();
+    private static final boolean RANDOM_PASS_ENABLED = false;
+
+    private final TestConfig mTestConfig;
+    private ActivityTestRule<? extends BaseActivity> mActivityTestRule;
+    private PageSwiper mSwiper;
+
+    public SwipeTest(TestConfig testConfig) {
+        mTestConfig = testConfig;
+    }
+
+    @Test
+    public void test() throws Throwable {
+        BaseActivity activity = mActivityTestRule.getActivity();
+
+        final int[] expectedValues = new int[mTestConfig.mTotalPages];
+        for (int i = 0; i < mTestConfig.mTotalPages; i++) {
+            expectedValues[i] = i;
+        }
+
+        int currentPage = 0, currentStep = 0;
+        assertStateCorrect(expectedValues[currentPage], activity);
+        for (int nextPage : mTestConfig.mPageSequence) {
+            // value change
+            if (mTestConfig.mStepToNewValue.containsKey(currentStep)) {
+                expectedValues[currentPage] = mTestConfig.mStepToNewValue.get(currentStep);
+                activity.updatePage(currentPage, expectedValues[currentPage]);
+                assertStateCorrect(expectedValues[currentPage], activity);
+            }
+
+            // config change
+            if (mTestConfig.mConfigChangeSteps.contains(currentStep++)) {
+                activity = FragmentActivityUtils.recreateActivity(mActivityTestRule, activity);
+                assertStateCorrect(expectedValues[currentPage], activity);
+            }
+
+            // page swipe
+            mSwiper.swipe(currentPage, nextPage);
+            currentPage = nextPage;
+            assertStateCorrect(expectedValues[currentPage], activity);
+        }
+    }
+
+    private void assertStateCorrect(int expectedValue, BaseActivity activity) {
+        onView(allOf(withId(R.id.text_view), isDisplayed())).check(
+                matches(withText(String.valueOf(expectedValue))));
+        activity.validateState();
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static List<TestConfig> getParams() {
+        List<TestConfig> tests = new ArrayList<>();
+
+        if (RANDOM_PASS_ENABLED) { // run locally after making larger changes
+            tests.addAll(generateRandomTests());
+        }
+
+        for (Class<? extends BaseActivity> activityClass : TEST_ACTIVITIES_ALL) {
+            tests.add(new TestConfig("full pass", asList(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0),
+                    NO_CONFIG_CHANGES, NO_MUTATIONS, 8, activityClass));
+
+            tests.add(new TestConfig("swipe beyond edge pages",
+                    asList(0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0), NO_CONFIG_CHANGES, NO_MUTATIONS, 4,
+                    activityClass));
+
+            tests.add(new TestConfig("config change", asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0),
+                    asList(3, 5, 7), NO_MUTATIONS, 7, activityClass));
+
+            tests.add(
+                    new TestConfig("regression1", asList(1, 2, 3, 2, 1, 2, 3, 4), NO_CONFIG_CHANGES,
+                            NO_MUTATIONS, 10, activityClass));
+
+            tests.add(new TestConfig("regression2", asList(1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 5),
+                    NO_CONFIG_CHANGES, NO_MUTATIONS, 10, activityClass));
+
+            tests.add(new TestConfig("regression3", asList(1, 2, 3, 2, 1, 2, 3, 2, 1, 0),
+                    NO_CONFIG_CHANGES, NO_MUTATIONS, 10, activityClass));
+        }
+
+        // mutations only apply to Fragment state persistence
+        tests.add(new TestConfig("mutations", asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0),
+                singletonList(8),
+                asList(Pair.create(0, 999), Pair.create(1, 100), Pair.create(3, 300),
+                        Pair.create(5, 500)), 7, FragmentAdapterActivity.class));
+
+        return checkTestNamesUnique(tests);
+    }
+
+    private static Collection<TestConfig> generateRandomTests() {
+        List<TestConfig> result = new ArrayList<>();
+
+        int id = 0;
+        for (int i = 0; i < 10; i++) {
+            // both adapters
+            for (Class<? extends BaseActivity> adapterClass : TEST_ACTIVITIES_ALL) {
+                result.add(createRandomTest(id++, 8, 50, 0, 0, 0.875, adapterClass));
+                result.add(createRandomTest(id++, 8, 10, 0.5, 0, 0.875, adapterClass));
+            }
+
+            // fragment adapter specific
+            result.add(
+                    createRandomTest(id++, 8, 50, 0, 0.125, 0.875, FragmentAdapterActivity.class));
+            result.add(createRandomTest(id++, 8, 10, 0.5, 0.125, 0.875,
+                    FragmentAdapterActivity.class));
+        }
+
+        return result;
+    }
+
+    /**
+     * @param advanceProbability determines the probability of a swipe direction being towards
+     *                           the next edge - e.g. if we start from the left, it's the
+     *                           probability that the next swipe will go right. <p> Setting it to
+     *                           values closer to 0.5 results in a lot of back and forth, while
+     *                           setting it closer to 1.0 results in going edge to edge with few
+     *                           back-swipes.
+     */
+    @SuppressWarnings("SameParameterValue")
+    private static TestConfig createRandomTest(int id, int totalPages, int sequenceLength,
+            double configChangeProbability, double mutationProbability, double advanceProbability,
+            Class<? extends BaseActivity> activityClass) {
+        Random random = new Random();
+
+        List<Integer> pageSequence = new ArrayList<>();
+        List<Integer> configChanges = new ArrayList<>();
+        List<Pair<Integer, Integer>> stepToNewValue = new ArrayList<>();
+
+        int pageIx = 0;
+        Double goRightProbability = null;
+        for (int currentStep = 0; currentStep < sequenceLength; currentStep++) {
+            if (random.nextDouble() < configChangeProbability) {
+                configChanges.add(currentStep);
+            }
+
+            if (random.nextDouble() < mutationProbability) {
+                stepToNewValue.add(Pair.create(currentStep, random.nextInt(10_000)));
+            }
+
+            boolean goRight;
+            if (pageIx == 0) {
+                goRight = true;
+                goRightProbability = advanceProbability;
+            } else if (pageIx == totalPages - 1) { // last page
+                goRight = false;
+                goRightProbability = 1 - advanceProbability;
+            } else {
+                goRight = random.nextDouble() < goRightProbability;
+            }
+            pageSequence.add(goRight ? ++pageIx : --pageIx);
+        }
+
+        return new TestConfig("random_" + id, pageSequence, configChanges, stepToNewValue,
+                totalPages, activityClass);
+    }
+
+    private static List<TestConfig> checkTestNamesUnique(List<TestConfig> configs) {
+        Set<String> names = new HashSet<>();
+        for (TestConfig config : configs) {
+            names.add(config.toString());
+        }
+        assertThat(names.size(), is(configs.size()));
+        return configs;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        Log.i(getClass().getSimpleName(), mTestConfig.toFullSpecString());
+
+        mActivityTestRule = new ActivityTestRule<>(mTestConfig.mActivityClass, true, false);
+        mActivityTestRule.launchActivity(BaseActivity.createIntent(mTestConfig.mTotalPages));
+
+        ViewPager2 viewPager = mActivityTestRule.getActivity().findViewById(R.id.view_pager);
+        RecyclerView recyclerView = (RecyclerView) viewPager.getChildAt(0); // HACK
+        mSwiper = new PageSwiper(mTestConfig.mTotalPages, recyclerView);
+
+        if (Build.VERSION.SDK_INT < 16) { // TODO(b/71500143): remove temporary workaround
+            RecyclerView mRecyclerView = (RecyclerView) viewPager.getChildAt(0);
+            mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
+        }
+
+        onView(withId(R.id.view_pager)).check(matches(isDisplayed()));
+    }
+
+    private static class TestConfig {
+        final String mMessage;
+        final List<Integer> mPageSequence;
+        final Set<Integer> mConfigChangeSteps;
+        /** {@link Map.Entry#getKey()} = step, {@link Map.Entry#getValue()} = new value */
+        final Map<Integer, Integer> mStepToNewValue;
+        final int mTotalPages;
+        final Class<? extends BaseActivity> mActivityClass;
+
+        /**
+         * @param stepToNewValue {@link Pair#first} = step, {@link Pair#second} = new value
+         */
+        TestConfig(String message, List<Integer> pageSequence,
+                Collection<Integer> configChangeSteps,
+                List<Pair<Integer, Integer>> stepToNewValue,
+                int totalPages,
+                Class<? extends BaseActivity> activityClass) {
+            mMessage = message;
+            mPageSequence = pageSequence;
+            mConfigChangeSteps = new HashSet<>(configChangeSteps);
+            mStepToNewValue = mapFromPairList(stepToNewValue);
+            mTotalPages = totalPages;
+            mActivityClass = activityClass;
+        }
+
+        private static Map<Integer, Integer> mapFromPairList(List<Pair<Integer, Integer>> list) {
+            Map<Integer, Integer> result = new HashMap<>();
+            for (Pair<Integer, Integer> pair : list) {
+                Integer prevValueAtKey = result.put(pair.first, pair.second);
+                assertThat("there should be only one value defined for a key", prevValueAtKey,
+                        equalTo(null));
+            }
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return mActivityClass.getSimpleName() + ": " + mMessage;
+        }
+
+        String toFullSpecString() {
+            return String.format(
+                    "Test: %s\nPage sequence: %s\nTotal pages: %s\nMutations {step1:newValue1, "
+                            + "step2:newValue2, ...}: %s",
+                    toString(),
+                    mPageSequence,
+                    mTotalPages,
+                    mStepToNewValue.toString().replace('=', ':'));
+        }
+    }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TestActivity.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TestActivity.java
deleted file mode 100644
index 74e2e4a..0000000
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TestActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.viewpager2.widget;
-
-import android.os.Bundle;
-
-import androidx.testutils.RecreatedActivity;
-import androidx.viewpager2.test.R;
-
-public class TestActivity extends RecreatedActivity {
-    @Override
-    public void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
-        setContentView(R.layout.activity_test_layout);
-
-        ViewPager2Tests.sAdapterStrategy.setAdapter((ViewPager2) findViewById(R.id.view_pager));
-    }
-}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ViewPager2Tests.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ViewPager2Tests.java
deleted file mode 100644
index 48d55f7..0000000
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ViewPager2Tests.java
+++ /dev/null
@@ -1,669 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.viewpager2.widget;
-
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static android.support.test.espresso.matcher.ViewMatchers.withText;
-import static android.view.View.OVER_SCROLL_NEVER;
-
-import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.arrayWithSize;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.espresso.ViewAction;
-import android.support.test.espresso.action.ViewActions;
-import android.support.test.filters.MediumTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.Preconditions;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import androidx.testutils.FragmentActivityUtils;
-import androidx.viewpager2.test.R;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ViewPager2Tests {
-    private static final Random RANDOM = new Random();
-    private static final int[] sColors = {
-            Color.parseColor("#BBA9FF00"),
-            Color.parseColor("#BB00E87E"),
-            Color.parseColor("#BB00C7FF"),
-            Color.parseColor("#BBB30CE8"),
-            Color.parseColor("#BBFF00D0")};
-
-    /** mean of injecting different adapters into {@link TestActivity#onCreate(Bundle)} */
-    static AdapterStrategy sAdapterStrategy;
-
-    interface AdapterStrategy {
-        void setAdapter(ViewPager2 viewPager);
-    }
-
-    @Rule
-    public final ActivityTestRule<TestActivity> mActivityTestRule;
-    @Rule
-    public ExpectedException mExpectedException = ExpectedException.none();
-
-    // allows to wait until swipe operation is finished (Smooth Scroller done)
-    private CountDownLatch mStableAfterSwipe;
-
-    public ViewPager2Tests() {
-        mActivityTestRule = new ActivityTestRule<>(TestActivity.class, true, false);
-    }
-
-    private void setUpActivity(AdapterStrategy adapterStrategy) {
-        sAdapterStrategy = Preconditions.checkNotNull(adapterStrategy);
-        mActivityTestRule.launchActivity(null);
-
-        ViewPager2 viewPager = mActivityTestRule.getActivity().findViewById(R.id.view_pager);
-        viewPager.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            @Override
-            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-                // coming to idle from another state (dragging or setting) means we're stable now
-                if (newState == SCROLL_STATE_IDLE) {
-                    mStableAfterSwipe.countDown();
-                }
-            }
-        });
-
-        if (Build.VERSION.SDK_INT < 16) { // TODO(b/71500143): remove temporary workaround
-            RecyclerView mRecyclerView = (RecyclerView) viewPager.getChildAt(0);
-            mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER);
-        }
-
-        onView(withId(viewPager.getId())).check(matches(isDisplayed()));
-    }
-
-    @Before
-    public void setUp() {
-        sAdapterStrategy = null;
-
-        final long seed = RANDOM.nextLong();
-        RANDOM.setSeed(seed);
-        Log.i(getClass().getName(), "Random seed: " + seed);
-    }
-
-    public static class PageFragment extends Fragment {
-        private static final String KEY_VALUE = "value";
-
-        public interface EventListener {
-            void onEvent(PageFragment fragment);
-
-            EventListener NO_OP = new EventListener() {
-                @Override
-                public void onEvent(PageFragment fragment) {
-                    // do nothing
-                }
-            };
-        }
-
-        private EventListener mOnAttachListener = EventListener.NO_OP;
-        private EventListener mOnDestroyListener = EventListener.NO_OP;
-
-        private int mPosition;
-        private int mValue;
-
-        public static PageFragment create(int position, int value) {
-            PageFragment result = new PageFragment();
-            Bundle args = new Bundle(1);
-            args.putInt(KEY_VALUE, value);
-            result.setArguments(args);
-            result.mPosition = position;
-            return result;
-        }
-
-        @Override
-        public void onAttach(Context context) {
-            super.onAttach(context);
-            mOnAttachListener.onEvent(this);
-        }
-
-        @NonNull
-        @Override
-        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-                @Nullable Bundle savedInstanceState) {
-            return inflater.inflate(R.layout.item_test_layout, container, false);
-        }
-
-        @Override
-        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-            Bundle data = savedInstanceState != null ? savedInstanceState : getArguments();
-            setValue(data.getInt(KEY_VALUE));
-        }
-
-        @Override
-        public void onDestroy() {
-            super.onDestroy();
-            mOnDestroyListener.onEvent(this);
-        }
-
-        @Override
-        public void onSaveInstanceState(@NonNull Bundle outState) {
-            outState.putInt(KEY_VALUE, mValue);
-        }
-
-        public void setValue(int value) {
-            mValue = value;
-            TextView textView = getView().findViewById(R.id.text_view);
-            applyViewValue(textView, mValue);
-        }
-    }
-
-    private static void applyViewValue(TextView textView, int value) {
-        textView.setText(String.valueOf(value));
-        textView.setBackgroundColor(getColor(value));
-    }
-
-    private static int getColor(int value) {
-        return sColors[value % sColors.length];
-    }
-
-    @Test
-    public void fragmentAdapter_fullPass() throws Throwable {
-        testFragmentLifecycle(8, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0));
-    }
-
-    @Test
-    public void fragmentAdapter_activityRecreation() throws Throwable {
-        testFragmentLifecycle(7, Arrays.asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0),
-                PageMutator.NO_OP, new HashSet<>(Arrays.asList(3, 5, 7)));
-    }
-
-    @Test
-    public void fragmentAdapter_random() throws Throwable {
-        final int totalPages = 8; // increase when stress testing locally
-        final int sequenceLength = 20; // increase when stress testing locally
-        testFragmentLifecycle_random(totalPages, sequenceLength, PageMutator.NO_OP);
-    }
-
-    @Test
-    public void fragmentAdapter_random_withMutations() throws Throwable {
-        final int totalPages = 8; // increase when stress testing locally
-        final int sequenceLength = 20; // increase when stress testing locally
-        testFragmentLifecycle_random(totalPages, sequenceLength, PageMutator.RANDOM);
-    }
-
-    private void testFragmentLifecycle_random(int totalPages, int sequenceLength,
-            PageMutator pageMutator) throws Throwable {
-        List<Integer> pageSequence = generateRandomPageSequence(totalPages, sequenceLength);
-
-        Log.i(getClass().getName(),
-                String.format("Testing with a sequence [%s]", TextUtils.join(", ", pageSequence)));
-
-        testFragmentLifecycle(totalPages, pageSequence, pageMutator,
-                Collections.<Integer>emptySet());
-    }
-
-    @NonNull
-    private List<Integer> generateRandomPageSequence(int totalPages, int sequenceLength) {
-        List<Integer> pageSequence = new ArrayList<>(sequenceLength);
-
-        int pageIx = 0;
-        Double goRightProbability = null;
-        while (pageSequence.size() != sequenceLength) {
-            boolean goRight;
-            if (pageIx == 0) {
-                goRight = true;
-                goRightProbability = 0.7;
-            } else if (pageIx == totalPages - 1) { // last page
-                goRight = false;
-                goRightProbability = 0.3;
-            } else {
-                goRight = RANDOM.nextDouble() < goRightProbability;
-            }
-
-            pageSequence.add(goRight ? ++pageIx : --pageIx);
-        }
-
-        return pageSequence;
-    }
-
-    /**
-     * Test added when caught a bug: after the last swipe: actual=6, expected=4
-     * <p>
-     * Bug was caused by an invalid test assumption (new Fragment value can be inferred from number
-     * of instances created) - invalid in a case when we sometimes create Fragments off-screen and
-     * end up scrapping them.
-     **/
-    @Test
-    public void fragmentAdapter_regression1() throws Throwable {
-        testFragmentLifecycle(10, Arrays.asList(1, 2, 3, 2, 1, 2, 3, 4));
-    }
-
-    /**
-     * Test added when caught a bug: after the last swipe: actual=4, expected=5
-     * <p>
-     * Bug was caused by mSavedStates.add(position, ...) instead of mSavedStates.set(position, ...)
-     **/
-    @Test
-    public void fragmentAdapter_regression2() throws Throwable {
-        testFragmentLifecycle(10, Arrays.asList(1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 5));
-    }
-
-    /**
-     * Test added when caught a bug: after the last swipe: ArrayIndexOutOfBoundsException: length=5;
-     * index=-1 at androidx.viewpager2.widget.tests.ViewPager2Tests$PageFragment.onCreateView
-     * <p>
-     * Bug was caused by always saving states of unattached fragments as null (even if there was a
-     * valid previously saved state)
-     */
-    @Test
-    public void fragmentAdapter_regression3() throws Throwable {
-        testFragmentLifecycle(10, Arrays.asList(1, 2, 3, 2, 1, 2, 3, 2, 1, 0));
-    }
-
-    /** Goes left on left edge / right on right edge */
-    @Test
-    public void fragmentAdapter_edges() throws Throwable {
-        testFragmentLifecycle(4, Arrays.asList(0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0));
-    }
-
-    private interface PageMutator {
-        void mutate(PageFragment fragment);
-
-        PageMutator NO_OP = new PageMutator() {
-            @Override
-            public void mutate(PageFragment fragment) {
-                // do nothing
-            }
-        };
-
-        /** At random modifies the page under Fragment */
-        PageMutator RANDOM = new PageMutator() {
-            @Override
-            public void mutate(PageFragment fragment) {
-                Random random = ViewPager2Tests.RANDOM;
-                if (random.nextDouble() < 0.125) {
-                    int delta = (1 + random.nextInt(5)) * sColors.length;
-                    fragment.setValue(fragment.mValue + delta);
-                }
-            }
-        };
-    }
-
-    /** @see this#testFragmentLifecycle(int, List, PageMutator, Set) */
-    private void testFragmentLifecycle(final int totalPages, List<Integer> pageSequence)
-            throws Throwable {
-        testFragmentLifecycle(totalPages, pageSequence, PageMutator.NO_OP,
-                Collections.<Integer>emptySet());
-    }
-
-    /**
-     * Verifies:
-     * <ul>
-     * <li>page content / background
-     * <li>maximum number of Fragments held in memory
-     * <li>Fragment state saving / restoring
-     * </ul>
-     */
-    private void testFragmentLifecycle(final int totalPages, List<Integer> pageSequence,
-            final PageMutator pageMutator, Set<Integer> activityRecreateSteps) throws Throwable {
-        final AtomicInteger attachCount = new AtomicInteger(0);
-        final AtomicInteger destroyCount = new AtomicInteger(0);
-        final boolean[] wasEverAttached = new boolean[totalPages];
-        final PageFragment[] fragments = new PageFragment[totalPages];
-
-        final int[] expectedValues = new int[totalPages];
-        for (int i = 0; i < totalPages; i++) {
-            expectedValues[i] = i;
-        }
-
-        setUpActivity(new AdapterStrategy() {
-            @Override
-            public void setAdapter(ViewPager2 viewPager) {
-                viewPager.setAdapter(
-                        ((FragmentActivity) viewPager.getContext()).getSupportFragmentManager(),
-                        new ViewPager2.FragmentProvider() {
-                            @Override
-                            public Fragment getItem(final int position) {
-                                // if the fragment was attached in the past, it means we have
-                                // provided it with the correct value already; give a dummy one
-                                // to prove state save / restore functionality works
-                                int value = wasEverAttached[position] ? -1 : position;
-                                PageFragment fragment = PageFragment.create(position, value);
-
-                                fragment.mOnAttachListener = new PageFragment.EventListener() {
-                                    @Override
-                                    public void onEvent(PageFragment fragment) {
-                                        attachCount.incrementAndGet();
-                                        wasEverAttached[fragment.mPosition] = true;
-                                    }
-                                };
-
-                                fragment.mOnDestroyListener = new PageFragment.EventListener() {
-                                    @Override
-                                    public void onEvent(PageFragment fragment) {
-                                        destroyCount.incrementAndGet();
-                                    }
-                                };
-
-                                fragments[position] = fragment;
-                                return fragment;
-                            }
-
-                            @Override
-                            public int getCount() {
-                                return totalPages;
-                            }
-                        }, ViewPager2.FragmentRetentionPolicy.SAVE_STATE);
-            }
-        });
-
-        final AtomicInteger currentPage = new AtomicInteger(0);
-        verifyCurrentPage(expectedValues[currentPage.get()]);
-        int stepCounter = 0;
-        for (int nextPage : pageSequence) {
-            if (activityRecreateSteps.contains(stepCounter++)) {
-                FragmentActivityUtils.recreateActivity(mActivityTestRule,
-                        mActivityTestRule.getActivity());
-            }
-            swipe(currentPage.get(), nextPage, totalPages);
-            currentPage.set(nextPage);
-            verifyCurrentPage(expectedValues[currentPage.get()]);
-
-            // TODO: validate Fragments that are instantiated, but not attached. No destruction
-            // steps are done to them - they're just left to the Garbage Collector. Maybe
-            // WeakReferences could help, but the GC behaviour is not predictable. Alternatively,
-            // we could only create Fragments onAttach, but there is a potential performance
-            // trade-off.
-            assertThat(attachCount.get() - destroyCount.get(), isBetween(1, 4));
-
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    final int page = currentPage.get();
-                    PageFragment fragment = fragments[page];
-                    pageMutator.mutate(fragment);
-                    expectedValues[page] = fragment.mValue;
-                }
-            });
-        }
-    }
-
-    private void swipe(int currentPageIx, int nextPageIx, int totalPages)
-            throws InterruptedException {
-        if (nextPageIx >= totalPages) {
-            throw new IllegalArgumentException("Invalid nextPageIx: >= totalPages.");
-        }
-
-        if (currentPageIx == nextPageIx) { // dedicated for testing edge behaviour
-            if (nextPageIx == 0) {
-                swipeRight(); // bounce off the left edge
-                return;
-            }
-            if (nextPageIx == totalPages - 1) { // bounce off the right edge
-                swipeLeft();
-                return;
-            }
-            throw new IllegalArgumentException(
-                    "Invalid sequence. Not on an edge, and currentPageIx/nextPageIx pages same.");
-        }
-
-        if (Math.abs(nextPageIx - currentPageIx) > 1) {
-            throw new IllegalArgumentException(
-                    "Specified nextPageIx not adjacent to the current page.");
-        }
-
-        if (nextPageIx > currentPageIx) {
-            swipeLeft();
-        } else {
-            swipeRight();
-        }
-    }
-
-    private Matcher<Integer> isBetween(int min, int max) {
-        return allOf(greaterThanOrEqualTo(min), lessThanOrEqualTo(max));
-    }
-
-    @Test
-    public void viewAdapter_edges() throws Throwable {
-        testViewAdapter(4, Arrays.asList(0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0),
-                Collections.<Integer>emptySet());
-    }
-
-    @Test
-    public void viewAdapter_fullPass() throws Throwable {
-        testViewAdapter(8, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0),
-                Collections.<Integer>emptySet());
-    }
-
-    @Test
-    public void viewAdapter_activityRecreation() throws Throwable {
-        testViewAdapter(7,
-                Arrays.asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0),
-                new HashSet<>(Arrays.asList(3, 5, 7)));
-    }
-
-    @Test
-    public void saveState_parcelWriteRestore() throws Throwable {
-        // given
-        Bundle superState = createIntBundle(42);
-        ViewPager2.SavedState state = new ViewPager2.SavedState(superState);
-        state.mRecyclerViewId = 700;
-        state.mAdapterState = new Parcelable[]{createIntBundle(1), createIntBundle(2),
-                createIntBundle(3)};
-
-        // when
-        Parcel parcel = Parcel.obtain();
-        state.writeToParcel(parcel, 0);
-        final String parcelSuffix = UUID.randomUUID().toString();
-        parcel.writeString(parcelSuffix); // to verify parcel boundaries
-        parcel.setDataPosition(0);
-        ViewPager2.SavedState recreatedState = ViewPager2.SavedState.CREATOR.createFromParcel(
-                parcel);
-
-        // then
-        assertThat("Parcel reading should not go out of bounds", parcel.readString(),
-                equalTo(parcelSuffix));
-        assertThat("All of the parcel should be read", parcel.dataAvail(), equalTo(0));
-        assertThat(recreatedState.mRecyclerViewId, equalTo(700));
-        assertThat(recreatedState.mAdapterState, arrayWithSize(3));
-        assertThat((int) ((Bundle) recreatedState.getSuperState()).get("key"), equalTo(42));
-        assertThat((int) ((Bundle) recreatedState.mAdapterState[0]).get("key"), equalTo(1));
-        assertThat((int) ((Bundle) recreatedState.mAdapterState[1]).get("key"), equalTo(2));
-        assertThat((int) ((Bundle) recreatedState.mAdapterState[2]).get("key"), equalTo(3));
-    }
-
-    private Bundle createIntBundle(int value) {
-        Bundle bundle = new Bundle(1);
-        bundle.putInt("key", value);
-        return bundle;
-    }
-
-    private void testViewAdapter(final int totalPages, List<Integer> pageSequence,
-            Set<Integer> activityRecreateSteps) throws InterruptedException {
-        setUpActivity(new AdapterStrategy() {
-            @Override
-            public void setAdapter(final ViewPager2 viewPager) {
-                viewPager.setAdapter(
-                        new Adapter<ViewHolder>() {
-                            @NonNull
-                            @Override
-                            public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
-                                    int viewType) {
-                                return new ViewHolder(
-                                        LayoutInflater.from(viewPager.getContext()).inflate(
-                                                R.layout.item_test_layout, parent, false)) {
-                                };
-                            }
-
-                            @Override
-                            public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
-                                TextView view = (TextView) holder.itemView;
-                                applyViewValue(view, position);
-                            }
-
-                            @Override
-                            public int getItemCount() {
-                                return totalPages;
-                            }
-                        });
-            }
-        });
-
-        verifyCurrentPage(0);
-        int currentPage = 0;
-        int stepCounter = 0;
-        for (int nextPage : pageSequence) {
-            if (activityRecreateSteps.contains(stepCounter++)) {
-                FragmentActivityUtils.recreateActivity(mActivityTestRule,
-                        mActivityTestRule.getActivity());
-            }
-            swipe(currentPage, nextPage, totalPages);
-            currentPage = nextPage;
-            verifyCurrentPage(currentPage);
-        }
-    }
-
-    /**
-     * Verifies that the current page displays the correct value and has the correct color.
-     *
-     * @param expectedPageValue value expected to be displayed on the page
-     */
-    private void verifyCurrentPage(int expectedPageValue) {
-        onView(allOf(withId(R.id.text_view), isDisplayed())).check(
-                matches(allOf(withText(String.valueOf(expectedPageValue)),
-                        new BackgroundColorMatcher(getColor(expectedPageValue)))));
-    }
-
-    private static class BackgroundColorMatcher extends BaseMatcher<View> {
-        private final int mColor;
-
-        BackgroundColorMatcher(int color) {
-            mColor = color;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendText("should have background color: ").appendValue(mColor);
-        }
-
-        @Override
-        public boolean matches(Object item) {
-            ColorDrawable background = (ColorDrawable) ((View) item).getBackground();
-            return background.getColor() == mColor;
-        }
-    }
-
-    private void swipeLeft() throws InterruptedException {
-        performSwipe(ViewActions.swipeLeft());
-    }
-
-    private void swipeRight() throws InterruptedException {
-        performSwipe(ViewActions.swipeRight());
-    }
-
-    private void performSwipe(ViewAction swipeAction) throws InterruptedException {
-        mStableAfterSwipe = new CountDownLatch(1);
-        onView(allOf(isDisplayed(), withId(R.id.text_view))).perform(swipeAction);
-        mStableAfterSwipe.await(1, TimeUnit.SECONDS);
-    }
-
-    @Test
-    public void itemViewSizeMatchParentEnforced() {
-        mExpectedException.expect(IllegalStateException.class);
-        mExpectedException.expectMessage(
-                "Item's root view must fill the whole ViewPager2 (use match_parent)");
-
-        ViewPager2 viewPager = new ViewPager2(InstrumentationRegistry.getContext());
-        viewPager.setAdapter(new Adapter<ViewHolder>() {
-            @NonNull
-            @Override
-            public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-                View view = new View(parent.getContext());
-                view.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); // arbitrary fixed size
-                return new ViewHolder(view) {
-                };
-            }
-
-            @Override
-            public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
-                // do nothing
-            }
-
-            @Override
-            public int getItemCount() {
-                return 1;
-            }
-        });
-
-        viewPager.measure(0, 0); // equivalent of unspecified
-    }
-
-    @Test
-    public void childrenNotAllowed() throws Exception {
-        mExpectedException.expect(IllegalStateException.class);
-        mExpectedException.expectMessage("ViewPager2 does not support direct child views");
-
-        Context context = InstrumentationRegistry.getContext();
-        ViewPager2 viewPager = new ViewPager2(context);
-        viewPager.addView(new View(context));
-    }
-
-    // TODO: verify correct padding behavior
-    // TODO: add test for screen orientation change
-    // TODO: port some of the fragment adapter tests as view adapter tests
-}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/BaseActivity.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/BaseActivity.java
new file mode 100644
index 0000000..5e3f326
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/BaseActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 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 androidx.viewpager2.widget.swipe;
+
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.testutils.RecreatedActivity;
+import androidx.viewpager2.test.R;
+import androidx.viewpager2.widget.ViewPager2;
+
+public abstract class BaseActivity extends RecreatedActivity {
+    private static final String ARG_TOTAL_PAGES = "totalPages";
+
+    protected ViewPager2 mViewPager;
+    protected int mTotalPages;
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.activity_test_layout);
+
+        mViewPager = findViewById(R.id.view_pager);
+        mTotalPages = checkNotNull(getIntent().getExtras()).getInt(ARG_TOTAL_PAGES);
+
+        setAdapter();
+    }
+
+    protected abstract void setAdapter();
+
+    public abstract void validateState();
+
+    public abstract void updatePage(int pageIx, int newValue);
+
+    public static Intent createIntent(int totalPages) {
+        Intent intent = new Intent();
+        intent.putExtra(ARG_TOTAL_PAGES, totalPages);
+        return intent;
+    }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/FragmentAdapterActivity.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/FragmentAdapterActivity.java
new file mode 100644
index 0000000..2d670a8
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/FragmentAdapterActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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 androidx.viewpager2.widget.swipe;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertThat;
+
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.widget.ViewPager2;
+
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class FragmentAdapterActivity extends BaseActivity {
+    private static final Random RANDOM = new Random();
+
+    private AtomicInteger mAttachCount = new AtomicInteger(0);
+    private AtomicInteger mDestroyCount = new AtomicInteger(0);
+    private PageFragment[] mFragments;
+
+    @Override
+    protected void setAdapter() {
+        mFragments = new PageFragment[mTotalPages];
+
+        ViewPager2.FragmentProvider fragmentProvider = new ViewPager2.FragmentProvider() {
+            final boolean[] mWasEverAttached = new boolean[mTotalPages];
+
+            @Override
+            public Fragment getItem(final int position) {
+                PageFragment fragment = PageFragment.create(valueForPosition(position));
+
+                fragment.mOnAttachListener = new PageFragment.EventListener() {
+                    @Override
+                    public void onEvent(PageFragment fragment) {
+                        mAttachCount.incrementAndGet();
+                        mWasEverAttached[position] = true;
+                    }
+                };
+
+                fragment.mOnDestroyListener = new PageFragment.EventListener() {
+                    @Override
+                    public void onEvent(PageFragment fragment) {
+                        mDestroyCount.incrementAndGet();
+                    }
+                };
+
+                return mFragments[position] = fragment;
+            }
+
+            private int valueForPosition(int position) {
+                // only supply correct value ones; then rely on it being kept by Fragment state
+                return mWasEverAttached[position]
+                        ? RANDOM.nextInt() // junk value to be overridden by state saved value
+                        : position;
+            }
+
+            @Override
+            public int getCount() {
+                return mTotalPages;
+            }
+        };
+
+        mViewPager.setAdapter(getSupportFragmentManager(), fragmentProvider,
+                ViewPager2.FragmentRetentionPolicy.SAVE_STATE);
+    }
+
+    @Override
+    public void updatePage(int pageIx, int newValue) {
+        mFragments[pageIx].updateValue(newValue);
+    }
+
+    @Override
+    public void validateState() {
+        assertThat(mAttachCount.get() - mDestroyCount.get(),
+                allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(4)));
+    }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageFragment.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageFragment.java
new file mode 100644
index 0000000..23132bc
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageFragment.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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 androidx.viewpager2.widget.swipe;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.test.R;
+
+public class PageFragment extends Fragment {
+    private static final String ARG_VALUE = "value";
+
+    private int mValue;
+    EventListener mOnAttachListener = EventListener.NO_OP;
+    EventListener mOnDestroyListener = EventListener.NO_OP;
+
+    static PageFragment create(int value) {
+        PageFragment result = new PageFragment();
+        Bundle args = new Bundle(1);
+        args.putInt(ARG_VALUE, value);
+        result.setArguments(args);
+        return result;
+    }
+
+    @NonNull
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.item_test_layout, container, false);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        Bundle data = savedInstanceState != null ? savedInstanceState : getArguments();
+        updateValue(data.getInt(ARG_VALUE));
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mOnAttachListener.onEvent(this);
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putInt(ARG_VALUE, mValue);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mOnDestroyListener.onEvent(this);
+    }
+
+    void updateValue(int newValue) {
+        mValue = newValue;
+        ((TextView) getView()).setText(String.valueOf(newValue));
+    }
+
+    public interface EventListener {
+        void onEvent(PageFragment fragment);
+
+        EventListener NO_OP = new EventListener() {
+            @Override
+            public void onEvent(PageFragment fragment) {
+                // do nothing
+            }
+        };
+    }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiper.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiper.java
new file mode 100644
index 0000000..a17ef94
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiper.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 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 androidx.viewpager2.widget.swipe;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static androidx.core.util.Preconditions.checkArgumentNonnegative;
+import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
+
+import static org.hamcrest.CoreMatchers.allOf;
+
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.ViewActions;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.test.R;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class PageSwiper {
+    private CountDownLatch mStableAfterSwipe;
+    private final int mLastPageIx;
+
+    public PageSwiper(int totalPages, RecyclerView recyclerView) {
+        mLastPageIx = checkArgumentNonnegative(totalPages - 1);
+
+        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                // coming to idle from another state (dragging or setting) means we're stable now
+                if (newState == SCROLL_STATE_IDLE) {
+                    mStableAfterSwipe.countDown();
+                }
+            }
+        });
+    }
+
+    public void swipe(int currentPageIx, int nextPageIx) throws InterruptedException {
+        if (nextPageIx > mLastPageIx) {
+            throw new IllegalArgumentException("Invalid next page: beyond last page.");
+        }
+
+        if (currentPageIx == nextPageIx) { // dedicated for testing edge behaviour
+            if (nextPageIx == 0) {
+                swipeRight(); // bounce off the left edge
+                return;
+            }
+            if (nextPageIx == mLastPageIx) { // bounce off the right edge
+                swipeLeft();
+                return;
+            }
+            throw new IllegalArgumentException(
+                    "Invalid sequence. Not on an edge, and current page = next page.");
+        }
+
+        if (Math.abs(nextPageIx - currentPageIx) > 1) {
+            throw new IllegalArgumentException(
+                    "Specified next page not adjacent to the current page.");
+        }
+
+        if (nextPageIx > currentPageIx) {
+            swipeLeft();
+        } else {
+            swipeRight();
+        }
+    }
+
+    private void swipeLeft() throws InterruptedException {
+        swipe(ViewActions.swipeLeft());
+    }
+
+    private void swipeRight() throws InterruptedException {
+        swipe(ViewActions.swipeRight());
+    }
+
+    private void swipe(ViewAction swipeAction) throws InterruptedException {
+        mStableAfterSwipe = new CountDownLatch(1);
+        onView(allOf(isDisplayed(), withId(R.id.text_view))).perform(swipeAction);
+        mStableAfterSwipe.await(1, TimeUnit.SECONDS);
+    }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/ViewAdapterActivity.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/ViewAdapterActivity.java
new file mode 100644
index 0000000..8830561
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/ViewAdapterActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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 androidx.viewpager2.widget.swipe;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+import androidx.viewpager2.test.R;
+
+public class ViewAdapterActivity extends BaseActivity {
+    @Override
+    protected void setAdapter() {
+        mViewPager.setAdapter(new RecyclerView.Adapter<ViewHolder>() {
+            @NonNull
+            @Override
+            public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+                    int viewType) {
+                return new ViewHolder(
+                        LayoutInflater.from(ViewAdapterActivity.this).inflate(
+                                R.layout.item_test_layout, parent, false)) {
+                };
+            }
+
+            @Override
+            public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+                TextView view = (TextView) holder.itemView;
+                view.setText(String.valueOf(position));
+            }
+
+            @Override
+            public int getItemCount() {
+                return mTotalPages;
+            }
+        });
+    }
+
+    @Override
+    public void validateState() {
+        // do nothing
+    }
+
+    @Override
+    public void updatePage(int pageIx, int newValue) {
+        throw new IllegalStateException("not implemented");
+    }
+}
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
index 11c73f2..e5f1b3f 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -455,11 +455,6 @@
                 getClass().getSimpleName() + " does not support direct child views");
     }
 
-    /** @see RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener) */
-    public void addOnScrollListener(RecyclerView.OnScrollListener listener) {
-        mRecyclerView.addOnScrollListener(listener);
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // TODO(b/70666622): consider margin support